Pinning and Address Stability
Pinning is a type-system guarantee that a value will remain at a fixed memory address (address stability) and will not be moved (std::mem::swap, std::mem::replace, or by-value move) from the moment it is pinned until its destructor (Drop::drop) finishes executing.
Why Address Stability Requires Preventing &mut T
In safe Rust, any caller holding a mutable reference &mut T can move T to a new memory location without consuming the reference—by calling std::mem::swap(&mut a, &mut b) or std::mem::replace(&mut a, new_val).
Why `&mut T` Breaks Address Stability:
Addr 0x1000: val_a (Self-referential) Addr 0x2000: val_b
| |
+--------- std::mem::swap(&mut a, &mut b) +
v v
Addr 0x1000: val_b Addr 0x2000: val_a (Moved! Internal pointers broken!)
Therefore, to guarantee address stability for a type T that is sensitive to its own address (!Unpin), the type system must restrict safe callers from ever obtaining a plain &mut T once the value has been pinned.
The Pinning Guarantee Contract
Once a value of a !Unpin type T is placed behind a pinned pointer Pin<P> (such as Pin<&mut T> or Pin<Box<T>>), two strict invariants hold:
- No movement: The value
Twill never be moved out of its memory address. - Drop before deallocation: The memory backing
Twill never be deallocated or repurposed until<T as Drop>::drop(&mut self)has been called on that exact memory address. (Becausestd::mem::forget(Pin<Box<T>>)is safe and leaks the heap allocation without reusing the memory, leaking a heap-pinned value does not violate the drop guarantee; however, stack pinning must ensure the stack frame cannot exit without runningDrop.)
Stack Pinning (std::pin::pin!) vs. Heap Pinning (Box::pin)
| Pinning Method | Syntax | Allocation Cost | Resulting Type |
|---|---|---|---|
| Stack pinning (Rust 1.68+) | let pinned = std::pin::pin!(expr); |
0 (pinned directly in current stack frame) |
Pin<&mut T> |
| Heap pinning | let pinned = Box::pin(expr); |
1 heap allocation (Box<T>) |
Pin<Box<T>> |
use std::marker::PhantomPinned;
use std::pin::{Pin, pin};
struct AddrTracker {
label: &'static str,
// Marks `AddrTracker` as `!Unpin` so its address is locked once pinned:
_pin: PhantomPinned,
}
impl AddrTracker {
fn log_address(self: Pin<&Self>) {
println!("{:?} pinned at {:p}", self.label, &*self);
}
}
fn main() {
let stack_pinned: Pin<&mut AddrTracker> = pin!(AddrTracker {
label: "stack-pinned",
_pin: PhantomPinned,
});
let heap_pinned: Pin<Box<AddrTracker>> = Box::pin(AddrTracker {
label: "heap-pinned",
_pin: PhantomPinned,
});
stack_pinned.as_ref().log_address();
heap_pinned.as_ref().log_address();
}
"stack-pinned" pinned at 0x16fdfee80
"heap-pinned" pinned at 0x600001794040