When moving breaks your data
You build a struct that stores a pointer to one of its own fields. Maybe it is a linked list node that caches a reference to avoid allocation, or a parser state machine that holds a slice pointing to its own buffer. You compile it, run it, and it works perfectly. Then you wrap it in a Box, pass it to a helper function, or let the compiler optimize the stack layout. Suddenly the pointer points to garbage. The memory address changed. Rust moved the value behind your back.
Pin<T> exists to lock a value to its memory address so it never moves after initialization. It trades Rust's default flexibility for address stability. You use it when your data structure relies on its own location in memory.
The concept: pinning by address
Rust's default behavior is to let values move freely. The compiler copies structs to the stack, shifts them during optimization, or drops and reallocates them. This flexibility is what makes Rust fast. It also breaks self-referential types. A self-referential type holds a pointer or reference to its own interior. If the struct moves, the pointer still points to the old address. The data and the pointer drift apart.
Think of a house with internal plumbing. The pipes connect the kitchen to the basement. If you pick up the house and move it to a new lot, the pipes stay connected to the house. They move with it. But if the house is just a tent, and you slide it across the lawn, the tent moves but the stakes stay in the ground. The interior layout shifts relative to the outside. Pin<T> drives a stake through the value. Once pinned, the compiler and runtime guarantee the address never changes. You trade flexibility for stability.
A minimal example
The following example shows how to pin a self-referential struct on the stack and write a pointer to its own field.
use std::pin::Pin;
/// A struct that points to its own data field.
struct SelfReferential {
data: String,
/// Points to the `data` field inside this same struct.
self_ptr: *const String,
}
fn main() {
// Allocate on the stack first.
let mut s = SelfReferential {
data: String::from("stable"),
self_ptr: std::ptr::null(),
};
// Pin the mutable reference. The compiler now forbids moving `s`.
let mut pin: Pin<&mut SelfReferential> = Pin::new(&mut s);
// We need unsafe to write the raw pointer, but the pin guarantees
// the address won't change after this line.
unsafe {
// SAFETY:
// 1. `pin` is a `Pin<&mut SelfReferential>`, so `s` cannot move.
// 2. `self_ptr` is initialized to null and will be overwritten.
// 3. The pointer targets `data`, which lives inside the same struct.
let ptr = Pin::get_unchecked_mut(&mut pin);
ptr.self_ptr = &ptr.data as *const String;
}
// Reading through the pointer is safe because the value is pinned.
let value = unsafe { &*pin.get_ref().self_ptr };
println!("Pinned value: {}", value);
}
Walk through what happens at compile time. Pin::new takes a mutable reference and wraps it in a Pin. It does not actually pin the value in memory yet. It just gives you a type that promises not to move it. The unsafe block calls Pin::get_unchecked_mut to bypass the borrow checker and get a plain &mut T. You write the raw pointer. The compiler enforces that you cannot extract &mut T from a Pin<&mut T> except through Pin methods. This prevents moving. When you try to read the pointer later, the address is still valid because the compiler blocked any move operation.
Treat the pin as a contract. Once you hand a reference to Pin::new, you promise never to move the underlying value.
How the compiler enforces stability
Pin is a marker wrapper. It does not allocate memory. It does not change layout. It changes what the compiler allows you to do with the value. The real magic lives in the Unpin trait. Every type in Rust implements Unpin by default. If a type implements Unpin, Pin<T> is just a regular T. You can move it, swap it, or copy it. The pinning guarantee vanishes.
To actually pin a type, it must explicitly opt out by writing impl !Unpin for MyType. This tells the compiler the type is self-referential or relies on stable addresses. Once a type is !Unpin, the compiler blocks any operation that would move it out of a Pin. You cannot call .get_mut() on a Pin<&mut T> to extract a plain &mut T. You cannot swap it with another value. You cannot pass it to a function expecting &mut T. The type system enforces the stake in the ground.
If you try to extract a mutable reference from a pinned !Unpin type, the compiler rejects you with E0277 (trait bound not satisfied). It tells you the type does not implement Unpin, so it cannot be unpinned. You must work through Pin methods or use unsafe with a documented safety proof.
The community convention is to keep !Unpin implementations rare. Most types do not need it. Only mark a type as !Unpin when it actually holds interior pointers or references that would break on a move. Marking a type as !Unpin without a reason just makes it harder to use.
Realistic usage: async state machines
You will rarely write impl !Unpin yourself. The standard library and async runtimes do it for you. The most common case is Future. A Future is a state machine that tracks where it is in an async operation. It often holds references to its own internal buffers or cached pointers to avoid reallocation between .poll() calls. If the future moves, those internal pointers break.
Async runtimes pin futures on the stack or heap before polling them. The runtime calls .poll() repeatedly. Between polls, the future might suspend. The runtime guarantees the future stays at the same address so the next poll finds valid internal state.
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
/// A simple future that tracks its own progress.
struct CountFuture {
count: u32,
/// Points to `count` to avoid re-fetching the address on every poll.
count_ptr: *mut u32,
}
// Opting out of Unpin tells the compiler this type relies on stable memory.
impl !Unpin for CountFuture {}
impl Future for CountFuture {
type Output = u32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY:
// 1. `CountFuture` is `!Unpin`, so it cannot move while pinned.
// 2. `count_ptr` was initialized before pinning and points to `self.count`.
// 3. The pointer is only used to read/write `count` within this struct.
unsafe {
let count_ref = &mut *self.get_unchecked_mut().count_ptr;
*count_ref += 1;
if *count_ref >= 3 {
Poll::Ready(*count_ref)
} else {
Poll::Pending
}
}
}
}
Notice the self: Pin<&mut Self> signature in poll. The trait requires it. You cannot implement Future without accepting a pinned reference. The runtime pins the future once, then passes the pinned reference to every poll. The future never moves.
Convention aside: Box::pin is the standard way to create pinned futures for runtimes. It allocates on the heap and pins it immediately. Pin::new only works for stack-allocated values and requires you to prove they will not move. The community defaults to Box::pin for async tasks because heap allocation guarantees a stable address across function boundaries.
Pitfalls and compiler boundaries
The compiler will reject code that tries to move a !Unpin type out of a pin. You will see trait bound errors when you accidentally pass a Pin<&mut T> to a function expecting &mut T. The compiler says the type does not implement Unpin, so it cannot be unpinned. You cannot extract a mutable reference from a pinned reference. If you try, you get a type mismatch error.
Another trap is Pin::new_unchecked. It skips the compile-time check that the value is actually pinned. If you use it on a stack variable that later moves, you get undefined behavior. The compiler cannot catch it. You must guarantee the value lives at that address for the entire scope. Reserve new_unchecked for FFI or custom allocators where you control the memory layout. For everything else, use Pin::new or Box::pin.
Treating Pin as a smart pointer is a common mistake. It is a zero-cost wrapper. It adds no runtime overhead. It only changes the type system rules. If you need heap allocation, wrap it in Box first. Box<Pin<T>> is the standard pattern for owned, pinned data.
Working with fields inside a pinned struct requires projection. You cannot just write &mut pin.field because that would extract a mutable reference to the field, which could theoretically be moved. The compiler blocks it with E0596 (cannot borrow as mutable) or E0277. You must use Pin::map_unchecked_mut or a crate like pin-project to safely project mutable access to specific fields while keeping the rest pinned. Write the projection carefully. A wrong projection breaks the safety guarantee.
When to reach for Pin
Use Pin<T> when you are building a self-referential struct that caches pointers to its own fields and needs a stable memory address. Use Pin<T> when you are implementing a custom Future or state machine that suspends and resumes across multiple calls. Use Box::pin when you need to allocate a pinned value on the heap and pass it to an async runtime. Reach for plain references when your type does not hold interior pointers; Pin adds type complexity without safety benefits. Skip Pin::new_unchecked unless you are writing a custom allocator or interfacing with C code that manages its own memory layout. Trust the Unpin trait. If your type implements it, pinning does nothing.