In my previous post, I spent some time talking about the idea of
unsafe abstractions. At the end of the post, I mentioned that Rust
does not really have any kind of official guidelines for what kind of
code is legal in an unsafe block and what is not.What this means in
practice is that people wind up writing what
seems reasonable and
checking it against what the compiler does today. This is of course a
risky proposition since it means that if we start doing more
optimization in the compiler, we may well wind up breaking unsafe code
(the code would still compile; it would just not execute like it used
Now, of course, merely having published guidelines doesn’t entirely
change that dynamic. It does allow us to
assign blame to the unsafe
code that took actions it wasn’t supposed to take. But at the end of
the day we’re still causing crashes, so that’s bad.
This is partly why I have advocated that I want us to try and arrive
at guidelines which are
human friendly. Even if we have published
guidelines, I don’t expect most people to read them in practice. And
fewer still will read past the introduction. So we had better be sure
reasonable code works by default.
Interestingly, there is something of a tension here: the more unsafe code we allow, the less the compiler can optimize. This is because it would have to be conservative about possible aliasing and (for example) avoid reordering statements. We’ll see some examples of this as we go.
Still, to some extent, I think it’s possible for us to have our cake and eat it too. In this blog post, I outline a proposal to leverage unsafe abstaction boundaries to inform the compiler where it can be aggressive and where it must be conservative. The heart of the proposal is the intution that:
- when you enter the unsafe boundary, you can rely that the Rust type system invariants hold;
- when you exit the unsafe boundary, you must ensure that the Rust type system invariants are restored;
- in the interim, you can break a lot of rules (though not all the rules).
I call this the Tootsie Pop model: the idea is that an unsafe abstraction is kind of like a Tootsie Pop. There is a gooey candy interior, where the rules are squishy and the compiler must be conservative when optimizing. This is separated from the outside world by a hard candy exterior, which is the interface, and where the rules get stricter. Outside of the pop itself lies the safe code, where the compiler ensures that all rules are met, and where we can optimize aggressively.
One can also compare the approach to what would happen when writing a C plugin for a Ruby interpreter. In that case, your plugin can assume that the inputs are all valid Ruby objects, and it must produce valid Ruby objects as its output, but internally it can cut corners and use C pointers and other such things.
In this post, I will elaborate a bit more on the model, and in particular cover some example problem cases and talk about the grey areas that still need to be hammered out.