Unsafe Rust

Aliasing and Mutability

Aliasing occurs when two or more pointers or references refer to overlapping regions of memory. Rust's aliasing model—enforced by the borrow checker in safe code and modeled by Stacked Borrows and Tree Borrows (validated by Miri) in unsafe code—requires that mutable references (&mut T) are strictly exclusive (noalias), even when raw pointers exist alongside them.

Core Aliasing Rules for Unsafe Code

  1. No active &mut T may coexist with any other live reference (&T or &mut T) to overlapping memory: Even creating two &mut T references that point to the same address without reading or writing them is immediate undefined behavior.
  2. No mutation through a pointer derived from &T (unless inside UnsafeCell<T>): Casting &T to *const T and then to *mut T and writing to it (*(r as *const i32 as *mut i32) = 5) is unconditionally undefined behavior.
  3. Interleaving &mut T and derived *mut T invalidates the raw pointer: Under Stacked Borrows, each pointer/reference derives a permission tag on a per-location borrow stack. If you derive let raw = &mut x as *mut i32; and then read or write x directly (or through &mut x), the derived raw pointer is popped from the borrow stack and becomes invalid for future dereferences!
Stacked Borrows Invalidation Trap:

  let mut x = 10;
  let raw: *mut i32 = &mut x;   // Borrow Stack for `x`: [ Unique(x), SharedReadWrite(raw) ]
  x = 20;                       // Direct access to `x` POPS `raw` off the stack!
                                // Borrow Stack for `x`: [ Unique(x) ]
  unsafe { *raw = 30; }         // ❌ UB! `raw` tag is no longer on the borrow stack!

How to Safely Alias Memory Using Raw Pointers

To maintain multiple aliased mutable pointers to the same allocation (such as in a doubly linked list or intrusive graph):

  • Derive all aliased raw pointers from the same root raw pointer (such as Box::into_raw(Box::new(...)) or &raw mut x).
  • Do not create an intermediate &mut Node over the whole node while another raw pointer into that node is later used, unless the second pointer is re-derived from that &mut Node.
fn main() {
    let mut value: i32 = 10;

    // Create a root raw pointer via `&raw mut`:
    let ptr_a: *mut i32 = &raw mut value;
    // Copy the raw pointer so `ptr_a` and `ptr_b` share the same permission tag:
    let ptr_b: *mut i32 = ptr_a;

    // SAFETY: Both `ptr_a` and `ptr_b` share the same raw borrow provenance,
    // and no conflicting `&i32` or `&mut i32` reference exists during these accesses.
    unsafe {
        *ptr_a += 5;
        *ptr_b *= 2;
        println!("final value via ptr_a: {}", *ptr_a);
    }

    println!("final value via owner: {}", value);
}
final value via ptr_a: 30
final value via owner: 30

Overlapping vs. Non-Overlapping Memory Copies (ptr::copy vs. ptr::copy_nonoverlapping)

When copying raw blocks of elements between two raw pointers src and dst:

Function C Equivalent Allows src and dst Regions to Overlap?
std::ptr::copy(src, dst, count) memmove Yes (handles overlapping ranges safely)
std::ptr::copy_nonoverlapping(src, dst, count) memcpy No (overlapping ranges cause immediate UB, but slightly faster when disjointness is known)
use std::ptr;

fn main() {
    let mut buffer = [1, 2, 3, 4, 5];
    let base: *mut i32 = buffer.as_mut_ptr();

    // Shift `[1, 2, 3]` right by 1 index to overlap at `[1..4]`:
    // SAFETY: Both `base` (0..3) and `base.add(1)` (1..4) are within `buffer` (len 5),
    // and `ptr::copy` supports overlapping source and destination ranges.
    unsafe {
        ptr::copy(base, base.add(1), 3);
    }

    println!("after overlapping ptr::copy: {:?}", buffer);
}
after overlapping ptr::copy: [1, 1, 2, 3, 5]