Unsafe blocks

Rust provides the following unsafe superpowers:

  • Dereference a raw pointer
  • Call an unsafe function or method
  • Access or modify a mutable static variable
  • Implement an unsafe trait
  • Access fields of unions

Most of them require an unsafe block. When translating an unsafe block to the mental model, the use of the unsafe superpowers are made explict, preserving the safety comment if any.

Dereference a raw pointer

Dereferencing a raw pointer can be seen as an operation that takes a value of some robust pointer type and returns a place. The exact predicate of that robust type is not part of the mental model, only making the operation take Update<*const T> or Update<*mut T> is.

// SAFETY: The pointer is valid, aligned, and points to a safe value.
unsafe { *ptr }

would translate to:

// SAFETY: The pointer is valid, aligned, and points to a safe value.
*Update(ptr)

or more pedantically:

let proof = "The pointer is valid, aligned, and points to a safe value.";
*Update { proof, value: ptr }

Call an unsafe function or method

Calling an unsafe function or method is the same as calling a regular function or method, except that the parameters using the update type (here Update<*const T>) must be adapted.

// SAFETY: <justification for add>. <justification for read>.
unsafe { ptr.add(start).read() }

would translate to:

// SAFETY: <justification for add>. <justification for read>.
Update(Update(ptr).add(start)).read()

Note how the location of the usage of both unsafe superpowers had to be made explicit. In particular, the pedantic version could use 2 distinct proofs:

let proof = "justification for add";
let ptr = Update { proof, value: ptr }.add(start);
let proof = "justification for read";
Update { proof, value: ptr }.read()

Access or modify a mutable static variable

Accessing or modifying a mutable static variable can be seen as an operation that takes a mutable static variable with a proof and returns a place. The proof states that the mutable variable is not aliased while the place is alive.

static mut COUNTER: u32 = 0;
// SAFETY: <justification>.
unsafe { COUNTER += 1 };

would translate to:

static mut COUNTER: u32 = 0;
// SAFETY: <justification>.
access_static_mut!(COUNTER, Update(())) += 1;

We have to use a macro because we need to pass the mutable static as a name without accessing it.

Implement an unsafe trait

This doesn't require an unsafe block and will be treated in its own chapter.

Access fields of unions

Accessing a field of a union can be seen as an operation that takes a place to the union, the field to access, and a proof, and returns the place of the field. The proof states that accessing that field of that union for that lifetime is valid.

// SAFETY: <justification>.
unsafe { u.f1 }

would translate to:

// SAFETY: <justification>.
access_union_field!(u, f1, Update(()))

Similarly for mutable static variables, we also need a macro because we need to pass the union as a place and its field as a name.