Sep 4, 2023
In this blog, I will go over the current problems with the unsafe
keyword in Rust
and propose a new backwards compatible pair of keywords called itsfine
and
safe
to fix the problems.
Adding this might confuse new users with new keywords and existing keywords that do the same
thing but slightly differently and bloat the language a bit. So I won’t discuss if this is a
necessary change that should be done, but new languages can definitely consider this instead
of unsafe
.
DISCLAIMER: I have changed my mind about some things that were written here
after discussion on
Reddit, please read the last section of this blog. I will keep this post up as a useful resource
about the unsafe
keyword.
unsafe
works currently
In Rust, we use the unsafe
keyword in two ways, first way is inside a function
body as a block.
This tells the compiler that the code inside the unsafe {}
block breaks some
rules, but this is something that can only be checked by a human so there is no need to worry
and the author has made sure this does not actually break anything. So the
unsafe {}
block does not mean the code is definitely unsafe, it
means the code could be potentially unsafe, because the safety is checked by
a human instead of the compiler.
Note that it is not unsafe to call a function containing unsafe {}
blocks
So the author has to make sure the function containing unsafe {}
blocks is okay
to be called on all scenarios at the call site.
The second way is as a prefix in the function signature
This means that the function contains some unsafe code that breaks some rules, but it is safe
if you uphold some contract documented by the author, and it is the caller’s
responsibility to uphold the contract. So you need an unsafe {}
block like below
when calling this function since it is on the person calling the function to uphold the
contract.
unsafe
The are some problems with the way the unsafe
works right now
The first is the name. The unsafe
keyword does not mean the code is unsafe,
it really means the safety is checked by the author, a human instead of
the compiler, so mistakes can be made. The code inside an unsafe
block is
usually unsafe, but in a particular given context beyond the reasoning of the compiler, it
is safe, it is fine.
The second is when a function signature is marked unsafe
like in
unsafe fn foobar()
, unsafe code is allowed in the
entire body of the function without the unsafe {}
block.
Ideally we would still want the compiler to check for safety and only allow unsafe code in
small unsafe {}
blocks just like any other function. The
unsafe
prefix really means the unsafe parts of this function is actually
unsafe if you don’t uphold the contract. This does not mean the compiler should allow
unsafe code without unsafe {}
.
1
The compiler assumes that any function containing unsafe {}
blocks is safe to
call at the call site implicitly. A naive author could write unsafe code
that is only safe if some contract is upheld, but the compiler gives no warnings or signs
for the author to think about contracts at the call site.
itsfine
keyword
So I propose a new keyword to solve these problems, the itsfine
keyword. This
keyword would work little differently from the current unsafe
keyword.
The first use case is the same, instead of unsafe
, you will use
itsfine
. It also reads nicely, “hey compiler, it’s fine, I know what I am doing,
you can’t check this”.
Now let’s talk about the difference, a function containing an unsafe
block can be
called without unsafe {}
at the call site.
This won’t be the case for itsfine
.
fn foobar() {
itsfine {
// unsafe code
}
}
fn baz() {
itsfine { foobar(); } // needs itsfine {} block
}
A function with itsfine {}
blocks is assumed to be unsafe unless some contract is
upheld by default, unlike a function containing unsafe {}
blocks
which is assumed safe to call implicitly.
There are two problems with this propagation behavior
At some point we do want the propagation to stop, we don’t want to have to write
itsfine {}
to call standard library functions for example. We need a way to
tell the compiler that a function containing itsfine {}
blocks is fine to
call without the itsfine {}
block at the call site and there is no contract
to be upheld.
A function containing itsfine {}
blocks needs an
itsfine {}
block at the call site, but the signature of the function does not
give any clues of this. So documentation becomes incomplete and the user would have to
find out by compiling the program.
To solve these problems, we will introduce another keyword, the safe
keyword.
This keyword basically stops the propagation of the itsfine {}
block
We can actually think of safe
to be implicitly present for all functions, even
for completely safe function with no itsfine {}
blocks. Functions with
itsfine {}
blocks have an implicit itsfine
in their function
signature, so they need itsfine {}
block at the call site. The author can choose
to be explicit and add an itsfine
or explicitly remove it using
safe
.
itsfine fn foobar() { // itsfine here is optional
itsfine {
// unsafe code
}
}
fn baz() {
itsfine { foobar(); } // needs itsfine {} block
}
With this, the documentation can leave out the safe
in function signatures, but
then show itsfine
if the function signature has one.
The safe
can also be used in the body of the function,
fn foobar() {
safe {
itsfine {
// unsafe code
}
}
}
fn baz() {
foobar(); // no need for itsfine {} block
}
Note that the safe {}
block only blocks the propagation of the
itsfine {}
block, it does not allow you to write unsafe code.
This will be very useful for macros that want to generate itsfine {}
blocks but
without making the caller of the macro to have to insert safe
as part of their
function signature to make their function safe to call.
The unsafe
keyword and itsfine
+ safe
keywords can
co-exist. The table below tells how
Call site | Callee has itsfine |
Callee has safe |
Callee has unsafe |
---|---|---|---|
unsafe {} |
Allowed | Allowed | Allowed |
itsfine {} |
Allowed | Allowed | Allowed |
no blocks or safe {} |
Not allowed | Allowed | Not allowed |
Functions that have unsafe
in its signature also has a itsfine
in
its signature. A function that does not have unsafe
in its signature (but may or
may not contain unsafe {}
blocks) has a safe
in its signature. The
documentation can also show these.
Migration scripts or commands can also be provided to change unsafe
keywords into
itsfine
and safe
keywords.
Every function that does not have an unsafe
in its signature but has
unsafe {}
blocks can be turned into safe
functions with
itsfine {}
blocks instead of unsafe {}
blocks.
Will turn into
Every function that has unsafe
in its signature can be turned into an
itsfine
function with a itsfine {}
block in its body.
Will turn into
I initially started with itsfine
and !itsfine
(note the
!
) instead of itsfine
and safe
at the beginning, taking
inspiration from auto traits. The problem with !itsfine
is the function signature
What !itsfine
means here is that this function does not require an
itsfine {}
block at the call site, but it can also be interpreted as “foobar is
not fine”, which will get really confusing for new users. The latter is also what I got from
reviews.
Another alternative is to use unsafe
and safe
, this is of course not
backwards compatible with the current way unsafe
works. Also the keyword
itsfine
seems to carry more truthy meaning to when you would actually use unsafe
code, writing code beyond the reasoning of the compiler, not for actually writing unsafe code.
This section was added later post discussions on Reddit. While I still do think the name
itsfine
is much nicer than unsafe
, I am not sure if the propagating
behavior and the safe
keyword is right.
The unsafe
keyword is always used in the context of encapsulating unsafe code in
safe APIs, so the safe
keyword would just be spammed everywhere. This also make
sense, there is no point in using a safe language if you don’t encapsulate unsafe code in safe
APIs and take advantage of the language. Because encapsulating unsafe code is the most common
use case, authors already think about contracts.
Functions being declared unsafe
is not very common and most functions declared
unsafe
don’t even have unsafe
blocks in them or are raw C bindings
2.
I think what I was really looking for is a
lint error
to add a // SAFETY:
comment for every occurrence of the
unsafe
keyword.
Is Rust Used Safely by Software Developers? arXiv:2007.00752v1↩︎