Introduction
Self-referential structs — structs that contain pointers to their own fields — are fundamentally at odds with Rust's move semantics. Moving a value changes its memory address, which invalidates internal pointers. Pin<P> solves this by guaranteeing that a value will not be moved, making self-referential patterns safe.
Key Concepts
Pin<P>: A wrapper that guarantees the pointed-to value will not be moved from its current memory location.Unpin: A marker trait indicating a type can be safely moved even when pinned. Most types implementUnpinby default.- Self-referential struct: A struct containing a pointer to one of its own fields. Moving the struct invalidates the pointer.
pin!macro: Creates a pinned value on the stack (stable since Rust 1.68).
Real World Context
Rust's async/await generates self-referential structs (futures that hold references across .await points). Every async fn relies on Pin to ensure the future is not moved while references into it are live. Libraries like tokio and futures use Pin extensively.
Deep Dive
The Problem: Moves Invalidate Internal Pointers
ruststruct SelfRef { value: String, ptr: *const String, // Points to self.value } let mut s = SelfRef { value: String::from("hello"), ptr: std::ptr::null(), }; s.ptr = &s.value; // ptr now points to s.value // If s is moved, ptr still points to the OLD location! let s2 = s; // MOVE! s.ptr is now dangling!
After the move, s2.ptr points to the memory where s.value used to be. Dereferencing it is UB.
Pin Prevents Moves
rustuse std::pin::Pin; use std::marker::PhantomPinned; struct PinnedSelfRef { value: String, ptr: *const String, _pin: PhantomPinned, // Opts out of Unpin } impl PinnedSelfRef { fn new(val: &str) -> Pin<Box<Self>> { let mut boxed = Box::new(PinnedSelfRef { value: val.to_string(), ptr: std::ptr::null(), _pin: PhantomPinned, }); let self_ptr: *const String = &boxed.value; // SAFETY: We don't move the boxed value after setting the pointer unsafe { let mut_ref = Pin::as_mut(&mut Pin::new_unchecked(&mut *boxed)); // Actually, we need to set it before pinning: } boxed.ptr = self_ptr; // SAFETY: value will not be moved because it's in a Box unsafe { Pin::new_unchecked(boxed) } } fn get_value(self: Pin<&Self>) -> &str { &self.value } fn get_ptr_value(self: Pin<&Self>) -> &str { // SAFETY: ptr was set to point to self.value and we're pinned unsafe { &*self.ptr } } }
Once pinned, the value cannot be moved out. Any attempt to call std::mem::swap or move the value will fail to compile.
The pin! Macro for Stack Pinning
rustuse std::pin::pin; let pinned = pin!(String::from("hello")); // pinned: Pin<&mut String> // The String is pinned to the stack frame
This is simpler than Box::pin for values that don't need to outlive the current scope.
Pin and Async/Await
Every async function returns a type that implements Future. Futures that hold references across .await points are self-referential:
rustasync fn example() { let data = vec![1, 2, 3]; let reference = &data; // reference borrows data some_async_operation().await; // Suspend point! println!("{:?}", reference); // reference must still be valid }
The generated future struct holds both data and reference. If the future were moved between the .await and the println!, reference would dangle. Pin prevents this move.
Common Pitfalls
- Forgetting
PhantomPinned— Without it, your type implementsUnpin, and Pin provides no protection.PhantomPinnedopts out ofUnpin. - Moving after
Pin::new_unchecked— If you pin a value and then move it anyway (through unsafe code), you violate the Pin contract and cause UB. - Confusing
Pin<Box<T>>withBox<Pin<T>>—Pin<Box<T>>pins the value inside the Box.Box<Pin<T>>boxes a Pin, which is rarely what you want.
Best Practices
- Use
Box::pin()for heap-pinned values — It's the simplest way to create aPin<Box<T>>. - Use the
pin!macro for stack pinning — Avoids heap allocation when the pinned value doesn't need to outlive the scope. - Implement
Unpinonly if your type has no self-references — Most types areUnpinby default. Only opt out (withPhantomPinned) if you actually need pin guarantees.
Summary
- Self-referential structs require
Pinto prevent moves that would invalidate internal pointers. Pin<P>guarantees the pointed-to value will not be moved.PhantomPinnedopts a type out ofUnpin, making Pin actually protect against moves.- Async/await relies on Pin because futures can be self-referential.
- Use
Box::pin()for heap pinning andpin!for stack pinning.
Code Examples
use std::pin::Pin;
use std::marker::PhantomPinned;
/// A self-referential struct where `ptr` points to `data`.
struct SelfReferential {
data: String,
ptr: *const String,
_pin: PhantomPinned, // !Unpin: cannot be moved once pinned
}
impl SelfReferential {
/// Create a new pinned instance on the heap.
fn new(data: &str) -> Pin<Box<Self>> {
let mut s = Box::new(SelfReferential {
data: data.to_string(),
ptr: std::ptr::null(),
_pin: PhantomPinned,
});
// Set the self-referential pointer BEFORE pinning
s.ptr = &s.data as *const String;
// SAFETY: We will not move the value after this point
// because it's behind Pin<Box<_>>
unsafe { Pin::new_unchecked(s) }
}
/// Access the data through the self-referential pointer.
fn get_via_ptr(self: Pin<&Self>) -> &str {
// SAFETY: ptr is valid because self is pinned (cannot move)
unsafe { &*self.ptr }
}
}
let pinned = SelfReferential::new("hello");
assert_eq!(pinned.as_ref().get_via_ptr(), "hello");