use std::pin::Pin;
use std::task::{Context, Poll};
use std::future::Future;
structMyFuture {
state: State,
}
implFutureforMyFuture {
typeOutput = ();
fnpoll(self: Pin<&mutSelf>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// self is pinned, guaranteed not to moveletthis = self.get_mut();
Poll::Ready(())
}
}
2. Self-Referential Structures
use std::pin::Pin;
structNode {
value: i32,
// Self-reference: pointer to field within same struct
next: Option<Pin<Box<Node>>>,
}
Solution Patterns
Pattern 1: Pinning on Heap
use std::pin::Pin;
letfuture = async {
// async block creates a Future
};
// Pin future on heapletpinned: Pin<Box<dyn Future<Output = ()>>> = Box::pin(future);
// Now safe to poll
Pattern 2: Pinning with Pin::new_unchecked
use std::pin::Pin;
structSelfReferential {
data: String,
ptr: *constString, // Points to data field
}
implSelfReferential {
fnnew(data: String) -> Pin<Box<Self>> {
letmut boxed = Box::new(SelfReferential {
data,
ptr: std::ptr::null(),
});
letptr = &boxed.data as *constString;
boxed.ptr = ptr;
// SAFETY: boxed is on heap and won't moveunsafe { Pin::new_unchecked(boxed) }
}
fndata(&self) -> &str {
// SAFETY: ptr still valid because we're pinnedunsafe { &*self.ptr }
}
}
Pattern 3: Pin Projection
use std::pin::Pin;
structWrapper<T> {
inner: T,
extra: String,
}
impl<T: Unpin> Wrapper<T> {
// Safe projection: T is Unpinfnproject(self: Pin<&mutSelf>) -> Pin<&mut T> {
Pin::new(&mutself.get_mut().inner)
}
}
impl<T> Wrapper<T> {
// Unsafe projection: must maintain invariantsfnproject_unchecked(self: Pin<&mutSelf>) -> Pin<&mut T> {
// SAFETY: if Self is pinned, inner field is also pinnedunsafe {
Pin::new_unchecked(&mutself.get_unchecked_mut().inner)
}
}
}
Pattern 4: Pinning in Async Context
use std::pin::Pin;
use futures::Future;
asyncfnprocess_data() {
letmut state = String::new();
// This reference is held across awaitletstate_ref = &mut state;
some_async_operation().await;
// state_ref must remain valid
state_ref.push_str("data");
}
// Compiler ensures state doesn't move by pinning the Future
Pin Types
Type
Use Case
Example
Pin<&T>
Borrowed, immutable
Pin<&Foo>
Pin<&mut T>
Borrowed, mutable
Pin<&mut Foo>
Pin<Box<T>>
Owned on heap
Pin<Box<Foo>>
Pin<Arc<T>>
Shared ownership
Pin<Arc<Foo>>
Unpin Marker Trait
// Most types implement Unpin (safe to move)structMyType {
data: Vec<u8>,
}
// Unpin auto-implemented// Which types DON'T implement Unpin?// - Futures (from async/await)// - Generators// - Manually marked with PhantomPinneduse std::marker::PhantomPinned;
structNotUnpin {
data: String,
_pin: PhantomPinned, // Opts out of Unpin
}
Workflow
Step 1: Determine if Pin Needed
Need Pin when:
→ async/await (Future trait)
→ Self-referential struct
→ Implementing custom Future
→ Working with generators
Don't need Pin when:
→ Synchronous code
→ No self-references
→ Stack-allocated temporaries
→ Type is Unpin
Step 2: Choose Pinning Strategy
Heap pinning:
→ Box::pin(value)
→ Safe, most common
Stack pinning:
→ pin!(value) // macro in std
→ More complex, zero allocation
Unsafe pinning:
→ Pin::new_unchecked()
→ Require SAFETY comments
Step 3: Handle Projections
Projecting to field:
→ If T: Unpin → Safe with Pin::new
→ If !Unpin → Unsafe, need Pin::new_unchecked
→ Use pin-project crate for safety
Common Use Cases
Scenario
Need Pin?
async {} block
✅ Yes (Future)
Box<dyn Future>
✅ Yes
Self-referential struct
✅ Yes
Regular Vec/HashMap
❌ No
Stack variables
❌ No
No self-references
❌ No
Review Checklist
When working with Pin:
Pin actually necessary (async or self-ref)
Correct pinning strategy chosen (heap vs stack)
Unsafe projections have SAFETY comments
Type correctly implements/opts-out of Unpin
No accidental moves after pinning
Projection maintains structural pinning
Drop implementation respects pinning
Documentation explains why pinned
Verification Commands
# Check if type is Unpin
cargo expand# Verify async state machine
cargo expand --lib my_async_fn
# Test with miri
cargo +nightly miri test
Common Pitfalls
1. Forgetting to Pin Future
Symptom: Compilation error about poll signature
// ❌ Bad: Future not pinnedfnpoll_future(mut future: implFuture) {
future.poll(); // Error: no poll method
}
// ✅ Good: Pin the Futurefnpoll_future(mut future: Pin<&mutimplFuture>) {
future.as_mut().poll(cx); // OK
}
2. Moving Pinned Value
Symptom: Undefined behavior
// ❌ Bad: moving after pinningletpinned = Box::pin(value);
letmoved = *pinned; // Error: cannot move out of pinned// ✅ Good: work with pinned referenceletpinned = Box::pin(value);
letpinned_ref: Pin<&mut Value> = pinned.as_mut();