Rust Pin, Unpin, and Safe Projection
Pin is Rust’s promise that a value will not be moved to a different memory address through a particular pinned pointer. It exists for values—especially async futures and self-referential structures—that may contain references to their own fields and would be invalidated by an ordinary move.
The problem: moves can change addresses
In Rust, a move usually copies a value’s bytes to a new location and makes the old location unavailable. That is harmless for most values. An integer contains its data directly, and a String owns a separate heap allocation, so moving the String value does not move its characters.
A self-referential value is different. Imagine a structure containing a buffer and a pointer into that buffer:
struct Message {
bytes: Vec<u8>,
// Conceptually: a reference into bytes
view: *const u8,
}
If Message moves, bytes moves as part of the structure, but view still contains the old address. The pointer now refers to the wrong place. The same issue applies to a Rust reference, although safe Rust makes it difficult to construct this kind of structure without special techniques.
Before Pin, an API could not safely say, “you may keep this value here, but you must not move it.” A caller holding &mut T could use operations such as mem::replace or mem::swap to move the value. Pin adds a type-level boundary around that promise.
Pinning does not mean the value can never be destroyed, nor that its address is permanent for the rest of the process. It means the value will not be moved while it is being accessed through that pin. Its storage can still be dropped.
What Pin guarantees
Pin<P> wraps a pointer-like value P, such as Box<T>, &mut T, or &T. The important part is the pointee: Pin<Box<T>> pins the T in the heap allocation, not the Box pointer itself.
This is useful because the pointer can move while the allocation stays put:
let future = async {
// work that may suspend
};
let pinned = Box::pin(future);
Box::pin allocates the future and returns Pin<Box<_>>. Moving pinned into another variable moves only the small box handle. The future remains at the same heap address.
Pinning a stack value follows the same principle, but the value must be pinned before any operation that could move it. The standard library provides pinning tools for this purpose. A stack-pinned value cannot outlive its stack frame, while a heap-pinned value can be stored and passed around according to the owner of its Box.
A Pin is not automatically a general-purpose mutable reference. Calling Pin::get_mut is allowed only when the value is Unpin, because otherwise exposing an ordinary &mut T could allow the caller to move it.
Why futures use Pin
An async block or an async fn produces a future: a value representing work that may complete later. Calling its poll method asks whether it is complete now; if not, it records enough state to continue after a wake-up.
The Future trait deliberately receives a pinned receiver:
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output>;
An async future is compiled into a state machine. At a suspension point such as .await, it stores its current state and the data needed to resume. Some futures also contain references into their own stored state, directly or through another future. If the future moved between polls, those references could point at its former location.
An executor therefore typically pins a future once, then repeatedly polls it through Pin<&mut F>. The executor is free to move the owning Box; it is not free to move the future inside that box. This is the reason error messages involving Future, poll, and Pin<&mut ...> are so common when manually implementing executors or combinators.
Not every future is self-referential. Pinning is still part of the general Future interface because the interface must support futures that are, including compiler-generated async futures.
Unpin: when pinning is unnecessary
Unpin is an auto trait—a property Rust can automatically assign to types—that says a value remains safe to move even when accessed through Pin. Most ordinary types are Unpin.
For an Unpin type, pinning is effectively a convenience wrapper. Rust can safely recover &mut T from Pin<&mut T>, because moving that T cannot invalidate internal address-sensitive state.
A type can opt out and become !Unpin (“not Unpin”) when its address matters. Self-referential types commonly do this; PhantomPinned is a standard marker used to prevent automatic Unpin implementation. Compiler-generated futures may also be !Unpin.
This distinction lets APIs be flexible: generic code can accept Pin<&mut T>, while code that needs to move T can require T: Unpin.
Safe projection into pinned fields
Often a pinned structure contains both address-sensitive and ordinary fields. Accessing one of those fields is called projection: deriving a reference to a field from a reference to the containing structure.
Projection must preserve the pin guarantee. A pinned field should become Pin<&mut Field>, while an explicitly unpinned field can become an ordinary &mut Field. Doing this manually with unsafe is easy to get wrong, especially when Drop—the code that runs during destruction—is involved.
The pin-project crate provides a commonly used safe interface:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
#[pin_project::pin_project]
struct Wrapper<F> {
#[pin]
future: F,
label: String,
}
impl<F: Future> Future for Wrapper<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project();
this.future.poll(cx)
}
}
The #[pin] attribute tells the projection tool that future must remain pinned. label is not address-sensitive, so projection gives ordinary mutable access to it. The generated projection code maintains the necessary invariants without requiring the caller to write unsafe code.
The overall pattern is therefore: pin a value before its address matters; use Unpin to identify values that can still move; and use safe projection tools when accessing fields through a pinned structure.