As I understood it, whenever I use unsafe
, I must ensure that whichever unsafe functions or language constructs I use, the prerequisites for them being sound must be met. E.g., if I use get_unchecked
on a Vec
, then I must ensure that the underlying slice is big enough:
Safety
Calling this method with an out-of-bounds index is undefined behavior even if the resulting reference is not used.
But if I have to assume that Vec
can do anything as long as it's not unsafe behavior, then I simply can't do that reasoning. This is why I get irritated when I read something like (I'm exaggerating now, to clarify my point): "If you do this, then it's a logic error and then anything could happen as long as it's not unsafe behavior; we call this 'logic error'. Don't worry, you're still safe, nothing unsafe can ever happen because of this. Just anything else, so don't count on anything anymore (except that there's no memory violation)."
Rust's safety guarantees indeed protect me from certain (worse) errors, namely memory related errors. But this only holds if I don't use unsafe
anywhere in my code.
Even worse: If I must always assume that all code can contain logic-errors, then I can't soundly use unsafe
at all!
My point that I try to express is that Rust's safety guarantees would act like a "one-shot safety net" if I accept arbitrary non-unsafe behavior in case of logic-errors. (Correct me if I understand it wrong, but let me try to explain.) Some errors are so bad that I lose track what happens in my code, effectively leading to (let me cite the reference here) "erroneous" code and "unpredictable" behavior. Rust's gurantees that as long as I only use code which is not marked as unsafe
and as long as all unsafe
code is sound, it will make sure this will never lead to one of the things listed in Behavior considered undefined. That's good! But if I'm at that point where everything is unpredictable except that (i.e. if I accept that my error is so bad that reasoning about my program doesn't work anymore), then I can't ensure anymore that unsafe
code will be sound.
So I see three options here:
- Never use
unsafe
- Accept that
unsafe
might always be unsound
- If I use any safe code that causes unpredictable (but "safe") behavior (called "logic errors" in the Rust world), then I must ensure that encapsulation (see also @kpreid's post above) will contain this "unpredictable" behavior within a crate or module.
That is what I mean with one-shot: If an (isolated) part of my program causes an unbound logic-error, then this part would be tainted and cannot use unsafe
soundly anymore (edit: as long as that tainted part is involved and the error isn't somehow contained by encapsulation).
The problem with that happening in std::collections
would be that std::collections
(or even std
) gets "tainted", which is bad for writing unsafe
code.
So I think that std::collections
should not laxly rely on Rust's safety mechanisms (by not specifying what may happen for the sake of forward compatibility), because it will consume this "one-shot" safety net which Rust's safety mechanisms provide us with.