Language vision regarding safety guarantees
kornel:
- Safe code can be a victim of invalid assumptions or bugs in some unsafe code, and end up being a trigger of UB despite not being at fault (e.g. unsafe code can return
&mutto safe code that is non-exclusive or NULL. Safe code using that reference will blow up, but only because it was given an already-broken state).
Thanks for bringing that up. This is a case where safe code triggers undefined behavior while unsafe code causes it. I didn't mention it because I'm only looking at cases where safe code causes undefined behavior. I should probably modify the top post to at least mention that well-documented scenario (and say that this is off-topic).
kornel:
- Safe code may sabotage an
unsafeblock that relied on the safe code to behave in a certain sensible way.
This is exactly what this thread is about. The whole question being what is "a certain sensible way" (or in technical terms "its safety guarantees")? Currently the convention is that safety guarantees are the logic contract. There is the other extreme where there are no safety guarantees except for the standard library (the Rust Hypothesis). I'm saying crate authors should choose (defaulting to no safety guarantees, which is the safe choice).
kornel:
For example, if you have an
unsafeblock that stores a bunch of pointers in aVec, it will likely depend on theVecacting sensibly
Because Vec comes from the standard library, this case is rather simple. It's pretty uncontested that the standard library guarantees for safety that it is correct for logic (which matches the convention).
kornel:
In the second case whose fault it is may be very context dependent.
Indeed, and such context is whether the unit of implementation interacts with a client or a dependency:
- If it's a client, Rust is pretty clear that it cannot trust it for logic. So if a client gives you a trait implementation, then you can't trust that implementation to be correct for logic. But you can trust it for safety. This is useless for safe traits because they don't have safety guarantees (which are the safety requirements to implement), but useful for unsafe traits.
- If it's a dependency, Rust is pretty unclear. There's this convention that you can trust it for logic. But this contradicts that "safe code cannot cause UB".
kornel:
In the end, you have to trust that something in the language works as documented. 2+2 is safe, and has to return 4.
Again, everybody agrees on those examples. You can obviously trust for safety that the language and the standard library are correct for logic (their safety guarantees is their logic contract). The question is about all the other crates. Should the answer be the same for all crates? Should the crates be able to decide for themselves? Should the clients of those crates decide instead independently (possibly with differing opinions both among themselves and with the author of the crate)?
The current situation is that the answer is the same for all crates: they all guarantee for safety that they are correct for logic (whether they want it or not). My opinion is that crates should decide for themselves, restoring the statement that "(arbitrary) safe code cannot cause UB" (or equivalently that "safe code can only cause UB within its unit of implementation" or also that "the scope of unsafe is encapsulated within its unit of implementation") which permits auditing crates for safety without looking at how they are used (which is infeasible).
Discussion in the ATmosphere