Learn Rust With Entirely Too Many Linked Lists
33

Testing Stacked Borrows

Two minimal examples to internalize the model: the bad pattern (reference begets raw pointer; reference is reused; raw pointer is then UB) and the good pattern (raw pointer obtained via addr_of_mut! lives independently of any reference).

  1. Bad. The r as *mut i32 cast is shorthand for "reference, then erase to raw" — same provenance, derived from r. Subsequent write through r invalidates p.
    fn bad() {
        let mut x = 0i32;
        let r = &mut x;
        let p = r as *mut i32;   // p derived from r
        *r = 1;                  // re-using r pops p
        unsafe { *p = 2; }       // UB — p's tag is gone
    }
  2. Good. addr_of_mut!(x) does not produce a reference; the resulting raw pointer is rooted at x itself. No &mut exists to compete with it. Writes through p are sound as long as no overlapping reference is live.
    use std::ptr::addr_of_mut;
    
    fn good() {
        let mut x = 0i32;
        let p = addr_of_mut!(x);   // raw pointer with no parent reference
        unsafe {
            *p = 1;
            *p = 2;
        }
    }
  3. Same lesson for slices and boxes: slice.as_mut_ptr() gives you a raw pointer rooted at the slice's storage without manufacturing a &mut [T] for it. Box::into_raw(b) consumes the Box and returns a *mut T that owns nothing — the canonical move from owned to raw.
    let mut v: Vec<u8> = vec![1, 2, 3];
    let p: *mut u8 = v.as_mut_ptr();  // OK, rooted at the vec's buffer
    
    let b: Box<i32> = Box::new(42);
    let p: *mut i32 = Box::into_raw(b);  // box is gone; p owns the heap slot