What does `unsafe` mean?

I'm going to be a bit picky here - there are four things we might refer to when we say unsafe:

  1. The behavior of unsafe fn
  2. The behavior of unsafe {}
  3. The behavior of unsafe trait/unsafe impl
  4. The keyword unsafe

The first three I would say are all part of Rust's effect system (in distinct ways), alongside const. The last I would not, or would say that it is only inasmuch as it's a really poorly considered syntax for interacting with the effect system.

My impression is that you've been, largely, syntax-directed in your view - taking the stance that unsafe means one thing, and we just have to pin it down carefully enough. I strongly disagree, and consider the first three items in that list to be very distinct meanings. As a result, if by "what unsafe really means" you intend to find a single definition of (4), I think you're on a snipe hunt.

I feel that a clearer syntax for pedagogical purposes would be as follows:

// Any Foo implementation must uphold invariants
// that `unsafe` code is allowed to trust
trusted Trait Foo {
    fn foo() -> bool;
}

// This implementation of Foo agrees to uphold said invariants
trusted impl Foo for () {
    fn foo() -> bool { ... }
}

// This function may violate memory safety if its assumptions are violated
//
// Note that these may be invariants, not just preconditions -
// consider threading, which could modify a `static mut` or
// `atomic` while this function is executing.
unsafe fn bar() -> Whatever {
    // This code may invoke primitive `unsafe` effects 
    // (dereferencing raw pointers, accessing `static mut`s)
    // and may call other `unsafe fn`s
    ...
}

// This function will not ever violate memory safety, and
// may rely upon `trusted` code's behavior in ensuring
// that this is the case (such as `T::foo()`)
fn baz<T: trusted Foo>() -> Option<Whatever> {
    if T::foo() {
        None
    } else {
        // The enclosed `unsafe` code has had its assumptions upheld
        Some(mask<unsafe> bar())
    }
}

Here,

  • trusted is the restriction-form (inwards-propagating) dual of the untrusted effect,

    • untrusted denotes "the right to violate your documented properties without memory-safety consequences"
    • Thus, trusted denotes "the restriction that violating documented properties is morally equivalent to violating memory safety"
  • unsafe is the effect that permits dereferencing raw pointers and accessing mutable statics

    • Being an effect, the "calling unsafe functions" power is covered by the central "effects are infectious outwards" property, and isn't anything special - an effectful context can always call other functions with the same effect; that's just how it works.
  • mask<e: Maskable> is a generalized tool for masking effects and restrictions. For effects (like unsafe), it allows putting code that should need the marker inside code that lacks it. For restrictions (like trusted), it allows putting the marker on code that contains calls that lack it. In short:

  • unsafe fn a() {}; fn b() { mask<unsafe> a() } with "effect" polarity

  • fn a() {}; trusted fn b() { mask<trusted> a() } with "restriction" polarity

Personally, I would love to be able to use trusted on functions (to say that unsafe code is allowed to rely on them) rather than it only being possible to ascribe to traits. Similarly, there exists an RFC to allow attaching const to traits and impls, so they may be constrained on in bounds.

The current syntax isn't something with a single behavior we need to illuminate; it's a consequence of shoehorning multiple behaviors into a limited set of keywords to avoid breaking changes.

7 Likes