On this page
unsafe Blocks and the Soundness Contract
The
unsafekeyword does not "disable" safety checks; rather, it transfers the safety contract that the compiler normally guarantees to the programmer. You can dereference raw pointers, call unsafe functions, and access mutable static variables, but you must ensure that no matter what the caller does, it will not trigger UB (Undefined Behavior). The real difficulty in writing unsafe code is not "being able to write it," but rather wrapping it in a safe abstraction.
unsafe is Not "Disabling All Safety Checks"
An unsafe {} block enables only 5 capabilities; all other Rust safety checks remain in effect:
- Dereferencing raw pointers (
*const T,*mut T) - Calling
unsafe fn - Accessing or modifying
static mutvariables - Implementing
unsafe traits (e.g.,Send,Sync,GlobalAlloc) - Accessing fields of a
union
The borrow checker remains active within unsafe blocks — you cannot create two &mut T references pointing to the same data. This is UB, and the compiler will still reject it even within unsafe code.
The key to understanding unsafe: it is not "I take on all safety responsibilities," but rather "I take on the responsibility for the part that the compiler cannot automatically verify."
Soundness: The Boundary of Safety
The signature of an unsafe fn defines a contract. The caller must satisfy the preconditions, otherwise UB may occur. This pattern is ubiquitous in the standard library:
// Safe externally, unsafe internally — the caller only needs to pass correct arguments, no need to write unsafe
If the assertion passes, the subsequent unsafe operations are sound — mid <= len guarantees that the two slices do not overlap. If the assertion fails, it panics directly — it does not enter the unsafe region. This is the most common pattern in the standard library: safe code validates conditions, and unsafe code executes under the premise that those conditions hold.
Crucially, the soundness of unsafe code depends not only on the logic within the unsafe block but also on the invariants maintained by external safe code. If the unsafe code you write assumes certain invariants of a data structure (such as len <= cap), then all external safe code that modifies that data structure must maintain that invariant.
UB (Undefined Behavior): The List to Avoid
- Dereferencing null, dangling, or misaligned pointers
- Violating borrowing rules (even within unsafe code)
- Data races
- Calling functions with the wrong ABI
- Creating invalid values:
boolthat is neither 0 nor 1, null references,charthat is a surrogate - Unwinding across FFI boundaries (panicking within an
extern "C"function)
Miri: Automated UB Detection
Miri interprets Rust at the MIR level, tracking provenance and borrow states:
It can detect use-after-free, out-of-bounds access, data races, and alignment violations. Any crate containing unsafe should be run with Miri — while it cannot prove soundness, it can catch obvious UB.
References
- Rustonomicon: "What Unsafe Can Do", Soundness requirements
- Rust Reference: Behavior considered undefined
- Miri: github.com/rust-lang/miri
Keywords: unsafe, soundness, UB, Miri, safety contract, invariant, raw pointer