Rust 97 🦀 Pinning Memory (Pin
and Unpin)
Rust’s default assumption is that every value can move. Assignment moves, function calls move, returning from a function moves. For the vast majority of types this is harmless: a String owns a heap allocation and the pointer to that allocation travels with the struct when it moves. But some types contain pointers into their own memory. When such a value moves, those internal pointers still point to the old location, and dereferencing them becomes undefined behavior. Pinning is the mechanism that lets Rust guarantee a value will not move once it has been placed, without changing the language’s move semantics for everything else.
The chapter begins with the problem that pinning solves: self-referential types, which appear most prominently in the state machines generated by async blocks. It then explains the Pin<P> wrapper, the Unpin auto-trait that lets most types ignore pinning entirely, and the practical patterns for creating pinned values on the heap and on the stack. The later sections cover the harder territory: implementing Drop for types that may have been pinned, writing projection methods that lift pinning guarantees into struct fields, and the specific safety invariants that make all of this sound.
Key point: Pin<P> does not prevent movement by magic. It prevents safe code from obtaining a &mut T when T is not Unpin, which in turn prevents safe code from calling mem::swap or mem::replace. The guarantee is enforced through the type system, not at runtime.
Why pinning exists
The self-referential problem. A struct that holds a pointer to its own field is broken by movement. The pointer records an address, and the struct’s move changes that address without updating the pointer. In a language with a moving garbage collector, the collector can patch such pointers when it moves objects. Rust has no moving collector, and its move semantics are pervasive: values move on assignment, on function return, and when passed by value. A self-referential struct in Rust is therefore unsound unless something prevents it from moving after the pointer is established .
The async state machine. The most common place Rust programmers encounter this problem is async code. An async fn compiles into a state machine that implements Future. When a local variable is borrowed across an .await point, the generated state machine holds both the variable and the borrow inside the same struct. That borrow is a pointer into the struct’s own memory. If the state machine were moved after it had been polled, the internal reference would dangle. The Future::poll method therefore takes self: Pin<&mut Self>, which is the compiler’s way of saying that the state machine must not move once polling begins .
The intrusive data structure. Async futures are not the only self-referential types. Intrusive linked lists store the list pointers inside the elements themselves, so that adding an element to the list requires writing into the element’s memory. If an element could move after being linked, the neighboring elements’ pointers would become invalid. Pinning allows the list to rely on the element staying put, which is exactly what intrusive data structures require .
The Drop guarantee. Pinning is not only about preventing moves. The memory of a pinned value must also remain valid until its destructor runs. If the memory were deallocated or repurposed without calling drop, any pointers into that memory held by other parts of the data structure would dangle. The pinning contract therefore includes a promise that drop will be called before the storage is reused. This matters for intrusive structures where removing an element requires patching its neighbors’ pointers, an operation that happens in Drop .
The cost of a new concept. Rust could have solved self-referential types by making all values immovable, or by requiring heap allocation for everything. Both would impose unacceptable costs on ordinary code. Instead, pinning is opt-in: a type becomes “address-sensitive” only when it contains a marker that removes Unpin, and only then do the restrictions apply. The vast majority of Rust types are Unpin, which means Pin<&mut T> behaves exactly like &mut T for them. The complexity is confined to the types that need it .
The unsafe boundary. Pinning is a contract between safe code and unsafe code. The safe API of Pin cannot be used to violate the pinning guarantee, but constructing a Pin that actually pins a non-Unpin value requires an unsafe operation (or a safe helper like Box::pin that performs the unsafe work internally). Types that rely on pinning must also be careful in their Drop implementation, because drop receives &mut self even for values that were pinned. These obligations are why pinning is considered an advanced topic: it moves correctness reasoning into the hands of the type author .
a. The problem: self-referential types and movement
A self-referential type holds a pointer to one of its own fields. The following struct is a minimal example. The data field owns a String, and the ptr field points into that String‘s heap buffer. As long as the struct stays at the same address, ptr remains valid. When the struct moves, the String‘s heap buffer does not move (the buffer is owned by the String and lives on the heap), but the address of the String struct itself changes. If ptr pointed to the String‘s stack representation rather than its heap buffer, it would dangle. The distinction matters for understanding what pinning actually protects.
// A self-referential struct in the simplest possible form.
// The pointer points into the struct's own data.
struct SelfRef {
data: String,
ptr: *const u8, // points to data.as_ptr() — the heap buffer
}
impl SelfRef {
fn new(s: &str) -> Self {
let data = s.to_string();
let ptr = data.as_ptr(); // pointer to the heap allocation
SelfRef { data, ptr }
}
}
This particular example is actually safe to move, because the pointer points to the String‘s heap buffer, and that buffer’s address does not change when the SelfRef struct moves. The dangerous case is when the pointer points to a field’s stack address, or to a sub-slice of the struct’s own inline data. The following struct demonstrates that danger: the slice field points to a region inside data, and if data is an inline array rather than a heap-owning String, moving the struct invalidates the slice.
// A genuinely address-sensitive struct: the slice points into the
// struct's own inline data. Moving the struct makes the slice dangle.
struct InlineSelfRef {
data: [u8; 32], // stored inline in the struct
slice: *const [u8], // points into `data`
}
impl InlineSelfRef {
fn new(bytes: &[u8; 32]) -> Self {
let data = *bytes;
let slice = &data[..16] as *const [u8]; // points into `data`
InlineSelfRef { data, slice }
}
}
The async state machine case is the one most Rust programmers will encounter. When the compiler transforms an async block that borrows a local across an .await, it generates a struct containing both the local and the borrow. The borrow is a reference into the struct’s own memory. The following simplified state machine shows the shape: the reference field holds a pointer to the data field, and moving the enum would invalidate that pointer.
// Simplified representation of what the compiler generates for:
// async fn example() {
// let data = String::from("hello");
// let reference = &data; // borrows data
// some_io().await; // await point
// println!("{}", reference); // uses the borrow after await
// }
enum ExampleStateMachine {
Start,
Awaiting {
data: String,
reference: *const String, // points to `data` inside this variant
},
Done,
}
The compiler does not generate a raw pointer like this. It generates a reference with a lifetime, but the structural problem is identical: the future contains a reference to its own field. Pin is the mechanism that prevents the future from being moved after it has been placed in a stable location and polled .
b. Pin<P>, Unpin, and the safe API
Pin<P> is a wrapper around a pointer type P. The wrapper’s job is to restrict access to the pointee in a way that prevents safe code from moving it. The key restriction is on &mut T: for a Pin<&mut T>, safe code cannot obtain a &mut T unless T: Unpin. Without a &mut T, safe code cannot call mem::swap, mem::replace, or mem::take, which are the standard ways to move a value out of a mutable reference .
The Unpin trait is an auto-trait, which means the compiler implements it automatically for types that do not opt out. Every ordinary type—i32, String, Vec<T>, HashMap—is Unpin. For these types, Pin<&mut T> is essentially equivalent to &mut T, and the pinning guarantees are vacuous because the type does not care about its address. A type opts out of Unpin by containing PhantomPinned, a zero-sized marker type that removes the automatic implementation .
use std::marker::PhantomPinned;
use std::pin::Pin;
// A type that opts out of Unpin. The marker has no runtime size,
// but its presence tells the compiler not to implement Unpin.
struct PinnedType {
_pin: PhantomPinned,
}
// A type that remains Unpin, so Pin<&mut T> acts like &mut T.
struct NormalType {
value: i32,
}
The Pin API has two construction paths. Pin::new is safe for Unpin types: it wraps a pointer without any restriction, because the type does not need protection. Pin::new_unchecked is unsafe: the caller asserts that the pointee will not move again for the duration of its pinned lifetime. Box::pin is a safe helper that allocates on the heap and returns a Pin<Box<T>>, performing the unsafe work internally because the heap allocation’s address is stable .
use std::pin::Pin;
// Safe: T is Unpin, so pinning imposes no restriction.
let mut x = 42;
let pinned: Pin<&mut i32> = Pin::new(&mut x);
// Unsafe: the caller promises the boxed value will not move.
let boxed: Pin<Box<PhantomPinned>> = Box::pin(PhantomPinned);
The pin! macro, stabilized in Rust 1.68, provides stack pinning without a heap allocation. It takes a value by expression and produces a Pin<&mut T> that borrows a temporary local. The local lives in the enclosing block, and the Pin reference cannot escape that block. This is useful for polling a future to completion within a function without boxing it .
use std::pin::pin;
// Pin a value on the stack. The Pin<&mut T> borrows the local.
let mut future = pin!(async { 42 });
// future: Pin<&mut impl Future<Output = i32>>
The pin! macro is not a replacement for Box::pin. It cannot be used to return a pinned value from a function, because the local it pins is dropped when the function returns. It is suitable only for local polling loops and other scopes where the pinned value does not need to outlive the current stack frame .
c. Drop, projections, and the safety invariants
Implementing Drop for a type that may have been pinned requires care. The drop method receives &mut self, but if the value was pinned before being dropped, that mutable reference is effectively a Pin<&mut Self> obtained through Pin::get_unchecked_mut. The type author must therefore treat drop as if it took Pin<&mut Self> and avoid any operation that would move out of self or invalidate the pinning invariants .
impl Drop for PinnedType {
fn drop(&mut self) {
// If this value was pinned, `self` is effectively Pin<&mut Self>.
// Wrap it to enforce that at the type level inside this helper.
inner_drop(unsafe { Pin::new_unchecked(self) });
fn inner_drop(this: Pin<&mut PinnedType>) {
// Any cleanup that respects pinning goes here.
// Do not move out of `this`.
}
}
}
Projection methods lift a Pin<&mut Struct> into a Pin<&mut Field> for fields that are themselves address-sensitive. The unsafe map_unchecked_mut function performs this mapping, and the caller must guarantee that the field will not move as long as the struct does not move, and that the caller does not move out of the field through the returned reference. The pinning documentation describes this as “structural pinning”: a struct author decides which fields participate in the pinning guarantee and which do not .
impl PinnedType {
// Project a Pin<&mut Self> into a Pin<&mut Field>.
// Safety: the field is structurally pinned — it will not move
// independently of the containing struct.
fn project(self: Pin<&mut Self>) -> Pin<&mut PhantomPinned> {
unsafe { self.map_unchecked_mut(|s| &mut s._pin) }
}
}
The #[repr(packed)] attribute is incompatible with pinning. The compiler may move packed fields around to align them for dropping, which violates the guarantee that pinned fields do not move. A type that uses pinning must not be repr(packed) .
The Pin::set method provides a way to overwrite a pinned value. This might seem to violate pinning, but it is sound because set runs the destructor of the old value before writing the new one. The old value’s Drop has the opportunity to clean up any self-references, and the new value is placed at the same address. This is the mechanism that makes assignment to pinned locations possible without breaking the guarantee .
Complete Example Session
// ============================================
// PART 1: THE SETUP
// ============================================
// We build a small self-referential struct to demonstrate
// the pinning problem and its solution. The struct holds
// inline data and a pointer into that data.
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr;
// ============================================
// PART 2: THE PROBLEM TYPE
// ============================================
// A struct that points to its own inline data.
// Moving it invalidates `slice`.
struct InlineData {
buffer: [u8; 16],
slice: *const u8, // points into buffer
_pin: PhantomPinned, // opt out of Unpin
}
// ============================================
// PART 3: CONSTRUCTION ON THE HEAP
// ============================================
// Box::pin allocates the value and pins it. The heap
// address is stable, so the internal pointer stays valid.
impl InlineData {
fn new(bytes: [u8; 16]) -> Pin<Box<Self>> {
let mut boxed = Box::new(InlineData {
buffer: bytes,
slice: ptr::null(),
_pin: PhantomPinned,
});
// Set the internal pointer after the value is in its final place.
// Safety: the box is pinned and will not move.
let ptr = boxed.buffer.as_ptr();
unsafe {
let mut_ref = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).slice = ptr;
}
boxed
}
// Read the first byte through the self-pointer.
// Takes Pin<&Self> to signal that the method requires pinning.
fn first_byte(self: Pin<&Self>) -> u8 {
unsafe { *self.slice }
}
}
// ============================================
// PART 4: THE ACCESS METHODS
// ============================================
// Shared access is safe: Pin<&Self> can be obtained from
// Pin<Box<Self>> without unsafe code.
fn read_first(pinned: Pin<&InlineData>) -> u8 {
pinned.first_byte()
}
// ============================================
// PART 5: THE STACK PIN
// ============================================
// The pin! macro pins a local without heap allocation.
// The pinned value cannot escape the enclosing block.
fn stack_pinned_example() -> u8 {
let data = InlineData {
buffer: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
slice: ptr::null(), // will be set after pinning
_pin: PhantomPinned,
};
let mut pinned = std::pin::pin!(data);
// Set the self-pointer now that the value is pinned in place.
unsafe {
let mut_ref = pinned.as_mut();
let inner = Pin::get_unchecked_mut(mut_ref);
inner.slice = inner.buffer.as_ptr();
}
pinned.first_byte()
}
// ============================================
// PART 6: THE Unpin CONTRAST
// ============================================
// A normal type remains Unpin, so Pin<&mut T> gives back
// a &mut T through the safe `get_mut` method.
struct Normal {
value: i32,
}
fn unpin_example() {
let mut n = Normal { value: 42 };
let mut pinned = Pin::new(&mut n);
// Normal: Unpin, so we can get &mut Normal and modify it.
pinned.get_mut().value = 100;
}
// ============================================
// PART 7: THE PROJECTION
// ============================================
// Project a Pin<&mut InlineData> into a Pin<&mut [u8; 16]>.
// The buffer field is structurally pinned: it cannot move
// independently of the containing struct.
impl InlineData {
fn project_buffer(self: Pin<&mut Self>) -> Pin<&mut [u8; 16]> {
unsafe { self.map_unchecked_mut(|s| &mut s.buffer) }
}
}
// ============================================
// PART 8: THE DROP IMPLEMENTATION
// ============================================
// Treat drop as if it received Pin<&mut Self>.
// The pointer cleanup respects the pinning guarantee.
impl Drop for InlineData {
fn drop(&mut self) {
// The pointer is invalidated here, but the value is about
// to be destroyed, so no further access can occur.
self.slice = ptr::null();
}
}
// ============================================
// PART 9: THE Future CONNECTION
// ============================================
// Futures are polled through Pin<&mut Self>. This is the
// most common place Rust programmers see Pin in the wild.
use std::future::Future;
use std::task::{Context, Poll};
struct SimpleFuture {
done: bool,
_pin: PhantomPinned,
}
impl Future for SimpleFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
// Safety: we never move out of `self`.
let this = unsafe { self.get_unchecked_mut() };
if this.done {
Poll::Ready(())
} else {
this.done = true;
Poll::Pending
}
}
}
// ============================================
// PART 10: THE FULL PICTURE
// ============================================
// Pinning is a contract: the value stays put, and its storage
// stays valid until Drop runs. The type system enforces the
// contract through Unpin and the Pin wrapper.
fn main() {
// Heap-pinned self-referential value.
let pinned = InlineData::new([9; 16]);
assert_eq!(read_first(pinned.as_ref()), 9);
// Stack-pinned value.
assert_eq!(stack_pinned_example(), 1);
// Unpin type: pinning is transparent.
unpin_example();
}
The ten parts build a self-referential struct from first principles, show both heap and stack pinning, contrast the Unpin case, and connect the mechanics to the Future trait. The struct is deliberately small so that each part is visible, but the shape matches the real pattern: a value with internal pointers that must be placed in its final location before those pointers are initialized.
Quick Reference
Pinning Constructors
| Constructor | Safety | Allocation | Use Case |
|---|---|---|---|
Box::pin(x) | Safe | Heap | Owned pinned values |
pin!(x) | Safe | Stack | Local polling |
Pin::new(&mut x) | Safe if T: Unpin | None | Unpin types only |
Pin::new_unchecked(ptr) | Unsafe | None | Manual pinning contracts |
Pin Methods
| Method | Requirement | Returns |
|---|---|---|
get_mut | T: Unpin | &mut T |
get_unchecked_mut | Unsafe | &mut T |
as_ref | None | Pin<&T> |
as_mut | None | Pin<&mut T> |
set | None | Overwrites, runs Drop first |
map_unchecked_mut | Unsafe | Pin<&mut U> |
Unpin Behavior
| Type | Unpin? | Pin<&mut T> |
|---|---|---|
i32, String, Vec | Yes | Acts like &mut T |
PhantomPinned | No | Restricted |
async blocks | No | Must be pinned to poll |
Struct containing PhantomPinned | No | Restricted |
Safety Invariants
| Invariant | Meaning |
|---|---|
| No move after pin | The pointee stays at its address |
Storage valid until Drop | Memory not freed or repurposed |
Drop runs before reuse | Destructor called for pinned values |
No repr(packed) | Packed fields may be moved by compiler |
Best Practices
✅ Do This:
// Use Box::pin for owned futures.
let fut = Box::pin(async { 42 }); // ✅
// Use pin! for local polling.
let mut fut = pin!(async { 42 }); // ✅
// Use PhantomPinned to opt out of Unpin.
struct S { _pin: PhantomPinned } // ✅
// Treat drop as Pin<&mut Self> for address-sensitive types.
fn drop(&mut self) { inner_drop(unsafe { Pin::new_unchecked(self) }); } // ✅
// Use structural pinning for fields that must not move.
fn project(self: Pin<&mut Self>) -> Pin<&mut Field> { ... } // ✅
// Keep pinned values in their final location before setting self-pointers.
let mut boxed = Box::pin(value); // ✅
❌ Don’t Do This:
// Don't move out of a pinned value.
let pinned = Box::pin(x); let moved = *pinned; // ❌
// Don't use repr(packed) with pinning.
#[repr(packed)] struct S { _pin: PhantomPinned } // ❌
// Don't initialize self-pointers before pinning.
let mut x = SelfRef { ptr: &x.data }; // ❌
// Don't return a pin! result from a function.
fn f() -> Pin<&mut T> { pin!(value) } // ❌
// Don't use Pin::new for !Unpin types.
let pinned = Pin::new(&mut non_unpin_value); // ❌
// Don't assume Pin prevents all mutation.
pinned.set(new_value); // allowed, runs Drop first // ✅ but often surprising
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Moving a pinned value | Pin only restricts safe access; unsafe code can still move | Use Box::pin or pin!, avoid get_unchecked_mut for moves |
| Dangling self-pointer after move | Pointer set before the value reached its final address | Set pointers only after pinning |
pin! result escapes block | The macro pins a local that is dropped at block end | Use Box::pin for values that must outlive the scope |
| Drop implementation moves fields | drop receives &mut self and moves are easy | Wrap self in Pin::new_unchecked inside drop |
| Projection invalidates field guarantee | map_unchecked_mut returns a pinned field that can move | Ensure the field is structurally pinned |
repr(packed) with pinning | Compiler moves packed fields for alignment | Remove repr(packed) from pinned types |
Future not polled with Pin | Custom Future impl uses &mut self | Use self: Pin<&mut Self> in poll |
Real-World Examples
1. Heap-Pinned Future
let fut: Pin<Box<dyn Future<Output = ()>>> = Box::pin(async {
// async body
});
2. Stack-Pinned Future for Polling
let mut fut = pin!(async { 42 });
// poll fut.as_mut() in a loop
3. PhantomPinned Marker
use std::marker::PhantomPinned;
struct SelfRef { _pin: PhantomPinned }
4. Unsafe Pin Construction
let pinned = unsafe { Pin::new_unchecked(&mut value) };
// caller guarantees value will not move
5. Getting a Mutable Reference from Unpin
let mut x = 42;
let mut pinned = Pin::new(&mut x);
*pinned.get_mut() = 100; // safe: i32 is Unpin
6. Structural Projection
fn project(self: Pin<&mut Self>) -> Pin<&mut Field> {
unsafe { self.map_unchecked_mut(|s| &mut s.field) }
}
7. Drop with Pinning
fn drop(&mut self) {
inner_drop(unsafe { Pin::new_unchecked(self) });
}
8. Overwriting Pinned Data
let mut pinned = Box::pin(1i32);
pinned.as_mut().set(2); // runs Drop for 1, writes 2
9. Polling a Future Manually
fn poll_pinned<F: Future>(mut fut: Pin<&mut F>, cx: &mut Context) -> Poll<F::Output> {
fut.as_mut().poll(cx)
}
10. Pin a Boxed Self-Referential Struct
let mut b = Box::pin(SelfRef { _pin: PhantomPinned });
// initialize self-pointers through get_unchecked_mut
Visual
The Move Problem
┌─────────────────────────────────────────────────────────┐
│ BEFORE MOVE │
│ │
│ Address 0x1000: │
│ ┌───────────────────────────────────────────────────┐ │
│ │ data: [1, 2, 3, 4, ...] │ │
│ │ slice: 0x1000 ──────────────────────────────┐ │ │
│ └───────────────────────────────────────────────│───┘ │
│ │ │
│ points to the start of data │ │
│ ▼ │
│ AFTER MOVE (mem::swap or assignment) │
│ │
│ Address 0x2000: │
│ ┌───────────────────────────────────────────────────┐ │
│ │ data: [1, 2, 3, 4, ...] │ │
│ │ slice: 0x1000 ──────────────────────────────┐ │ │
│ └───────────────────────────────────────────────│───┘ │
│ │ │
│ still points to 0x1000, │ │
│ which is now invalid ▼ │
│ (DANGLING POINTER) │
└─────────────────────────────────────────────────────────┘
Pin’s Enforcement
┌─────────────────────────────────────────────────────────┐
│ Pin<&mut T> │
│ │
│ Safe methods available: │
│ as_ref() → Pin<&T> (shared, no move) │
│ as_mut() → Pin<&mut T> (reborrow, no move) │
│ set(x) → overwrite (runs Drop first) │
│ │
│ Blocked unless T: Unpin: │
│ get_mut() → &mut T (allows mem::swap) │
│ DerefMut → &mut T (allows replacement) │
│ │
│ The type system prevents safe code from obtaining │
│ the &mut T that would enable a move. │
│ │
│ Unsafe escape: get_unchecked_mut() → &mut T │
│ (caller promises not to move) │
└─────────────────────────────────────────────────────────┘
Heap vs Stack Pinning
┌─────────────────────────────────────────────────────────┐
│ Box::pin(value) pin!(value) │
│ │
│ ┌─────────────────────┐ ┌─────────────────┐ │
│ │ HEAP │ │ STACK │ │
│ │ ┌───────────────┐ │ │ ┌───────────┐ │ │
│ │ │ value │ │ │ │ value │ │ │
│ │ │ (stable addr)│ │ │ │(local addr)│ │ │
│ │ └───────────────┘ │ │ └───────────┘ │ │
│ │ │ │ │ │
│ │ Outlives function │ │ Cannot escape │ │
│ │ Returns Pin<Box> │ │ Returns Pin<&mut>│ │
│ │ Allocates │ │ No allocation │ │
│ └─────────────────────┘ └─────────────────┘ │
│ │
│ Use for owned futures Use for local polling │
└─────────────────────────────────────────────────────────┘
Unpin Decision Tree
┌─────────────────────────────────────────────────────────┐
│ Does the type contain a pointer to its own data? │
│ │
│ YES NO │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Add Phantom- │ │ Leave as is │ │
│ │ Pinned │ │ (Unpin) │ │
│ └───────────────┘ └───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ Must be │ │ Pin<&mut T> │ │
│ │ accessed via │ │ acts like │ │
│ │ Pin<P> │ │ &mut T │ │
│ └───────────────┘ └───────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
│ │ Drop must │ │
│ │ respect pin │ │
│ └───────────────┘ │
└─────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
Pin<P> | Wrapper that restricts safe access to prevent moves |
Unpin | Auto-trait for types safe to move when pinned |
PhantomPinned | Marker that opts out of Unpin |
Box::pin | Safe heap pinning, returns Pin<Box<T>> |
pin! | Safe stack pinning, returns Pin<&mut T> |
Pin::new | Safe only for T: Unpin |
Pin::new_unchecked | Unsafe manual pinning |
get_mut | Requires T: Unpin, returns &mut T |
get_unchecked_mut | Unsafe, caller must not move |
Drop | Must treat self as Pin<&mut Self> for pinned types |
repr(packed) | Incompatible with pinning |
Key takeaways:
- Pinning protects self-referential types from movement. A pointer into a value’s own memory becomes invalid if the value moves;
Pinprevents that movement through the type system. Unpinis the opt-out. Most types areUnpinand ignore pinning entirely. Only types containingPhantomPinnedare restricted.- Async state machines are the primary real-world case. Every future that holds a borrow across an
.awaitis self-referential, which is whyFuture::polltakesPin<&mut Self>. Box::pinis for ownership;pin!is for local polling. The box pins on the heap and can be returned; the macro pins on the stack and cannot escape its block.- The
Dropimplementation matters. If a type can be pinned, its destructor must not move out ofselfor invalidate the pinning invariants. - Projections require unsafe code. Lifting
Pin<&mut Struct>toPin<&mut Field>is sound only if the field is structurally pinned and will not move independently. Pindoes not prevent all mutation.setcan overwrite a pinned value because it runs the destructor first. The guarantee is about movement, not immutability.- Unsafe code can always break the contract.
get_unchecked_mutreturns a&mut Tregardless ofUnpin. The caller is responsible for not moving the value. repr(packed)is incompatible with pinning. The compiler may move packed fields for alignment, violating the pinning guarantee.- The contract extends to memory reuse. Pinned storage must not be freed or repurposed until
Drophas run.
Remember: Pinning is a contract between safe code and unsafe code, mediated by the Unpin trait. Pin<P> does not freeze memory; it restricts the safe API so that a value which has been placed in its final location cannot be moved through safe operations. Types that rely on this guarantee take on obligations: they must opt out of Unpin with PhantomPinned, their Drop implementation must respect the pinned state, and their projection methods must be written with the same care as any other unsafe code. The payoff is that Rust can express self-referential structures—futures, intrusive lists, generators—without a garbage collector and without giving up move semantics for the rest of the language.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!