32
Stacked Borrows
Stacked borrows is the operational semantics behind Rust's aliasing rules. Conceptually similar to TBAA-meets-restrict: every reference and raw pointer carries a tag, every memory location maintains a stack of currently-valid tags, and any access by tag X pops everything above X. If your tag has been popped, your access is UB. Tree borrows is a refinement that organizes tags as a tree rather than a stack — it permits some patterns stacked borrows rejects and rejects some it permits.
- Make a
&mut, take its address as*mut, write through the raw pointer, then write through the&mutagain — that last write pops the raw pointer's tag. Subsequent use of the raw pointer is UB. The fix is to useaddr_of_mut!(or&raw mut) to get a raw pointer that doesn't go through a reference at all, so its tag has no parent reference to invalidate it.// stacked borrows mental model: // stack at addr(x): [root] <- nothing borrowed yet // let r = &mut x; // stack at addr(x): [root, r_tag] <- r tag pushed // let p = r as *mut i32; // stack at addr(x): [root, r_tag, p_tag] <- p derived from r // *r = 5; <- writes via r_tag, pops p_tag // stack at addr(x): [root, r_tag] // *p = 6; <- p_tag is gone -> UB - The discipline this implies: pick one path to the memory and stick with it for the duration. If you need a raw pointer for the long haul, build it with
addr_of_mut!so it doesn't sit on top of a reference whose lifetime you'll outlive.use std::ptr::addr_of_mut; let mut x = 0i32; let p: *mut i32 = addr_of_mut!(x); // no reference created unsafe { *p = 1; *p = 2; } // no UB — there was never a competing reference