X-Git-Url: https://git.ralfj.de/web.git/blobdiff_plain/8e16357bf59d7322689055ef5d7de2c13cee54a3..456685f7e728ce397d7bfddc061656c781eb0834:/personal/_posts/2018-08-22-two-kinds-of-invariants.md?ds=inline diff --git a/personal/_posts/2018-08-22-two-kinds-of-invariants.md b/personal/_posts/2018-08-22-two-kinds-of-invariants.md index 785a9fd..fcf12ea 100644 --- a/personal/_posts/2018-08-22-two-kinds-of-invariants.md +++ b/personal/_posts/2018-08-22-two-kinds-of-invariants.md @@ -72,7 +72,7 @@ For `Vec` to work, however, `ptr` will be pointing to valid memory of size `cap This is an invariant that all the unsafe code implementing `Vec` maintains, and it is the invariant that the safe API surface of `Vec` expects the outside world to uphold. The reason this works is that the fields mentioned in this invariant are all private, so safe code cannot break the invariant and use that broken invariant to cause havoc. Again, the safety invariant is about ensuring safe execution of safe code. -Unsafe code can of course break this invariant, but then it us just Doing It Wrong (TM). +Unsafe code can of course break this invariant, but then it is just Doing It Wrong (TM). Thanks to privacy and an abstraction barrier, types in Rust can define *their own safety invariant*, and they can then expect the outside world to respect that invariant. As we have seen with `Vec`, when generic types are involved, these custom safety invariants will often have a "structural" element in that being safe at `Vec` is defined in terms of being safe at `T`. @@ -102,12 +102,17 @@ But that's okay, because this is carefully controlled unsafe code -- and by the > *Unsafe code only has to uphold safety invariants at the boundaries to safe code.* +Notice that the "boundary" is not necessarily where the `unsafe` block ends. +The boundary occurs where the unsafe code provides a public API that safe code is intended to use -- that might be at the module boundary, or it might even be at the crate level. +That is where safe code should be able to rely on safety, so that it can interact with the unsafe code without triggering undefined behavior, and hence that is where the safety invariants come into play. + This is in strong contrast to validity, which must *always* hold. Layout optimizations and LLVM's attributes are in effect throughout unsafe code, so it is never okay to ever have invalid data. +(With the sole restriction of data which *the compiler statically knows is not initialized*: If you write `let b: bool;`, that data in `b` is kept inaccessible *even to unsafe code*, and it does not have to satisfy any invariant. This works because the compiler knows about `b` not being initialized.) > *Unsafe code must always uphold validity invariants.* -So we clearly cannot just pick the same invariant for both. +So we clearly cannot just pick the same invariant for both, or else it would be impossible to write `Vec`. We *might* want to just ignore user-defined invariants when it comes to validity, but I think that would be ill-advised. First of all, validity is part of the definition of undefined behavior. @@ -180,7 +185,7 @@ My gut feeling is that it should not be (i.e., validity should require that `i32 I have talked about two kinds of invariants that come with every type, the safety invariant and the validity invariant. For unsafe code authors, the slogan summarizing this post is: -> *You must always be valid, but you must only be safe in safe code.* +> *You must always be valid, but you only must be safe in safe code.* I think we have enough experience writing unsafe code at this point that we can reasonably discuss which validity invariants make sense and which do not -- and I think that it is high time that we do so, because many unsafe code authors are wondering about these exact things all the time.