Course contentsShow
← Rust
Lesson 372 of 1,9104. Lifetimes and Advanced OwnershipFree lesson

Aliasing and Mutability in Unsafe Rust

Maintain safety invariants when constructing abstractions that exceed borrow checker capabilities.

In this lesson, you will learn how to maintain strict memory safety invariants when writing unsafe code that bypasses the borrow checker's aliasing and mutability rules.

The Aliasing Invariant

The Rust compiler relies on a fundamental promise: for any given piece of data, you may have either many immutable references (aliasing) or one single mutable reference, but never both at the same time. This "Aliasing XOR Mutability" rule allows the compiler to perform aggressive optimizations because it knows the data won't change unexpectedly while you are reading it.

When you enter an unsafe block, you are essentially telling the compiler, "I am taking manual responsibility for upholding this promise." If you create a mutable reference (&mut T) while an immutable reference (&T) still exists, you have violated the safety invariants of Rust. Even if your code doesn't crash immediately, the compiler's optimizer might reorder instructions based on the assumption that the immutable reference is stable, leading to undefined behavior.

Handling Overlapping Access

Suppose you are building a custom data structure—like a high-performance linked list or a manual buffer—where you need to interact with internal memory in ways the borrow checker doesn't understand. If you need both read and write access, you cannot simply cast pointers to create overlapping references.

fn safe_violation(data: &i32) {
    unsafe {
        // ERROR: You cannot create a mutable reference while 
        // a shared reference to the same data exists!
        let ptr = data as *const i32 as *mut i32;
        let mut_ref = &mut *ptr; 
        *mut_ref += 1; 
    }
}

Even though you used unsafe, you broke the contract. To maintain safety, you must ensure that the lifetime of your mutable access is strictly disjoint from the lifetime of any other access. You often do this by wrapping your raw pointers in a structure that uses UnsafeCell to interior-mutably manage access, or by ensuring you aren't holding any live references (&) to the memory while you perform your unsafe mutations. When unsafe code hides an alias, the compiler's guarantees evaporate.

Key Takeaway: Entering an unsafe block does not waive the Aliasing XOR Mutability rule; it shifts the burden of proof from the compiler to you, requiring that you ensure no two references to the same memory overlap in a way that allows simultaneous mutation and aliasing.