Advanced Lifetimes and Borrowing

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:

  1. No movement: The value T will never be moved out of its memory address.
  2. Drop before deallocation: The memory backing T will never be deallocated or repurposed until <T as Drop>::drop(&mut self) has been called on that exact memory address. (Because std::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 running Drop.)

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