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).
- Bad. The
r as *mut i32cast is shorthand for "reference, then erase to raw" — same provenance, derived fromr. Subsequent write throughrinvalidatesp.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 } - Good.
addr_of_mut!(x)does not produce a reference; the resulting raw pointer is rooted atxitself. No&mutexists to compete with it. Writes throughpare 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; } } - 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 theBoxand returns a*mut Tthat 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