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.
unsafeThe 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↩︎