Language vision regarding safety guarantees
ia0:
All crates can cause undefined behavior, even if they use
#![forbid(unsafe_code)].Let's consider a crate author that doesn't want to deal with undefined behavior. They write a crate
foowhich uses only safe code and forbids unsafe code. Another crate author writes a cratebarwhich depends onfoo. They write unsafe code which relies on the safety guarantees offoo(which is the logic contract offoo). It's all working.The initial author refactors their crate
fooand publish a patch or minor version.
And nothing should happen, because if the safety of bar depends on the functional correctness of safe APIs in foo, bar should depend on a very specific version of foo, like =1.2.3. Or better yet, vendor foo (maybe republishing it to crates.io as bar-foo - somewhat common across the ecosystem) or, what's actually the best solution, just copy the code from foo and paste on some module in bar.
That's because if some unsafe code depends on some safe code to not trigger UB, then the author of the unsafe code should review the safe code thoroughly. This is the responsibility of bar, the author of foo has nothing to do with it, and can not ever be blamed by UB. This is the axiom of Rust safety: safe code can't be blamed for UB in unsafe code, no matter what.
But what about robustness? (click for more details)
The best case scenario is if both pieces of code is written by the same author (and hopefully are in the same crate, or even better, in the same module). This is very common - the code that can trigger UB is usually restricted to a single module in a crate (perhaps with a number of submodules), and we should all aspire to make it possible to audit the unsafe code by reading just this module. If we depend on external crates, we should think about whether we depend on its correctness on matters of UB. If we do, that's probably dangerous, or at very least, it makes the lives of unsafe auditors very hard (they need to also read the code from the external library to assess whether your code triggers UB. Very bad). But if you do anyway, the code may become impossible to audit.
What about the stdlib? (click for more details)
Depending on libraries with vague versions like 1.3 (or worse, 1) is like depending on generic code - you can't control exactly what code you depend upon. Cargo works in mysterious ways when deciding what specific code you depend upon (and this can change if we tweak things like the resolver or if some other crate that is completely unrelated to ours depends on foo with a different minor version, or some feature flag enabled!).
For safe code, this is merely a nuisance. A minor version bump may introduce some regression or whatever. For unsafe code, this is safety critical and can never, ever happen. Just like unsafe code can't rely on generic safe code to determine whether it causes UB, it can't rely on the Cargo resolver to not trigger UB either.
This all means that unsafe code can't enjoy the usual workflow for safe code in Rust. We often need to do something in our own crate because depending on random code from other crates won't fly. This is less ergonomic for sure.
Discussion in the ATmosphere