Introduction
Some data structures contain pointers to themselves. If such a struct is moved in memory, its internal pointers become dangling. Pin is Rust's solution for guaranteeing that a value stays at its current memory address, making self-referential types safe.
Key Concepts
- Self-referential struct: A struct that contains a pointer to one of its own fields. Moving the struct invalidates the pointer.
- Pin<P>: A wrapper around a pointer P that guarantees the pointee will not be moved out.
- PhantomPinned: A marker type that opts a struct out of the Unpin trait, making it truly immovable once pinned.
Real World Context
Async/await in Rust desugars into state machines that may contain self-references — a local variable and a reference to it stored across an await point. Without Pin, the async runtime could move the Future and corrupt these internal references. Pin is what makes the entire async ecosystem safe.
Deep Dive
The core problem is straightforward. If a struct holds a pointer to itself, moving the struct breaks the pointer:
ruststruct SelfRef { value: String, ptr: *const String, // Points to value above } let mut s = SelfRef { value: String::from("hello"), ptr: std::ptr::null(), }; s.ptr = &s.value; // ptr now points to value's address // If we move s... let s2 = s; // value moves to new address! // s2.ptr still points to the OLD address — dangling!
Async functions create exactly this pattern. The state machine generated by the compiler stores both local variables and references to them:
rustasync fn example() { let data = vec![1, 2, 3]; let reference = &data[0]; // Self-reference within the state machine some_async_op().await; // Suspension point — state is saved println!("{reference}"); // reference must still be valid }
Pin<P> guarantees the pointee won't move:
rustuse std::pin::Pin; use std::marker::PhantomPinned; struct Unmovable { data: String, _pin: PhantomPinned, // Makes type !Unpin } let pinned: Pin<Box<Unmovable>> = Box::pin(Unmovable { data: String::from("hello"), _pin: PhantomPinned, }); // Can't move out of pinned! // let moved = *pinned; // Error!
Common Pitfalls
- Assuming all types need Pin — Most types have no self-references and implement Unpin, meaning Pin has no effect on them. Only types with internal pointers to themselves need pinning.
- Trying to create Pin on the stack without the pin! macro — Before Rust 1.68's
pin!macro, stack pinning required unsafe code. Usepin!orBox::pinfor safe pinning.
Best Practices
- Use Box::pin for heap pinning — It is the simplest and safest way to create a pinned value.
- Use the pin-project crate for custom futures — It provides safe pin projection macros that eliminate the need for unsafe code.
Summary
- Self-referential structs break if moved because internal pointers become dangling.
- Pin<P> guarantees the pointee won't move, making self-references safe.
- Async functions generate self-referential state machines, which is why Futures require pinning.
- PhantomPinned opts a type out of Unpin, making it truly immovable once pinned.
Code Examples
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfReferential {
data: String,
slice: *const str, // Will point into data
_pin: PhantomPinned,
}
impl SelfReferential {
fn new(data: String) -> Pin<Box<Self>> {
let res = Self {
data,
slice: std::ptr::null(),
_pin: PhantomPinned,
};
let mut boxed = Box::pin(res);
// Safe: we're setting up the self-reference
// before anyone can observe it
let slice: *const str = &boxed.data;
unsafe {
let mut_ref = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).slice = slice;
}
boxed
}
fn get_slice(self: Pin<&Self>) -> &str {
unsafe { &*self.slice }
}
}