How do I work with Pin in Rust?
Walkthrough
Pin is a wrapper around a pointer type (like &mut T or Box<T>) that prevents the pointed-to value from being moved in memory. This is crucial for self-referential types and is the foundation of Rust's async/await system.
Key concepts:
- Pinning ā Preventing a value from being moved after it's pinned
- Self-referential types ā Types that contain references to themselves
Unpintrait ā Types that can safely be moved even when pinned!Unpinā Types that must not be moved once pinned
Most types in Rust are Unpin, meaning they can be moved freely even through a Pin<&mut T>. The pin guarantees only matter for types that explicitly opt out with !Unpin (like compiler-generated futures).
When Pin matters:
- Writing async code with futures
- Implementing self-referential structs
- Building generators/state machines
- Working with
Futuretrait manually
Code Examples
Basic Pin with Box
use std::pin::Pin;
fn main() {
let boxed = Box::new(42);
// Pin the Box - now the value cannot be moved
let pinned: Pin<Box<i32>> = Pin::new(boxed);
// We can still read the value
println!("Value: {}", *pinned);
// i32 is Unpin, so we can get a mutable reference
// (Only possible because i32 doesn't care about being moved)
let mut pinned = pinned;
let reference = pinned.as_mut().get_mut();
*reference = 100;
println!("Modified: {}", *pinned);
}Pin with Unpin Types
use std::pin::Pin;
// Most types implement Unpin automatically
struct NormalStruct {
value: i32,
}
fn main() {
let mut boxed = Box::new(NormalStruct { value: 42 });
let mut pinned = Pin::new(boxed);
// NormalStruct is Unpin, so we can get mutable access
let reference = pinned.as_mut().get_mut();
reference.value = 100;
println!("Value: {}", reference.value);
// We can also unpin and move it
let unpinned: Box<NormalStruct> = pinned.into_inner();
let moved = unpinned; // Move is allowed!
println!("Moved: {}", moved.value);
}Self-Referential Struct (Conceptual)
use std::pin::Pin;
use std::marker::PhantomPinned;
// A self-referential struct (simplified example)
// In practice, this is very hard to construct safely
struct SelfReferential {
data: String,
// This would be a reference to `data`
// In real code, this requires unsafe and careful construction
pointer: *const str, // Raw pointer to part of `data`
_marker: PhantomPinned, // Opt out of Unpin
}
impl SelfReferential {
fn new(s: &str) -> Pin<Box<Self>> {
let mut boxed = Box::new(SelfReferential {
data: s.to_string(),
pointer: std::ptr::null(),
_marker: PhantomPinned,
});
// Set up self-reference (unsafe!)
let self_ptr: *const str = &boxed.data;
unsafe {
let self_mut = Box::as_mut(&mut boxed) as *mut SelfReferential;
(*self_mut).pointer = self_ptr;
}
// Pin it before returning
Pin::from(boxed)
}
fn get_data(&self) -> &str {
&self.data
}
fn get_pointer(&self) -> &str {
// Safe because we know pointer points to valid data
unsafe { &*self.pointer }
}
}
fn main() {
let pinned = SelfReferential::new("hello");
println!("Data: {}", pinned.get_data());
println!("Pointer: {}", pinned.get_pointer());
// Cannot move or get mutable access because of PhantomPinned
// let moved = pinned; // This would be prevented by the type system
}Working with Pinned Futures
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
// A simple future that completes after being polled once
struct OneShotFuture {
completed: bool,
}
impl Future for OneShotFuture {
type Output = &'static str;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.completed {
Poll::Ready("done")
} else {
self.completed = true;
Poll::Pending
}
}
}
fn main() {
let mut future = Box::pin(OneShotFuture { completed: false });
// Create a context for polling (simplified)
let waker = futures::task::noop_waker();
let mut cx = Context::from_waker(&waker);
// Poll the future
match future.as_mut().poll(&mut cx) {
Poll::Ready(result) => println!("Ready: {}", result),
Poll::Pending => println!("Pending..."),
}
// Poll again
match future.as_mut().poll(&mut cx) {
Poll::Ready(result) => println!("Ready: {}", result),
Poll::Pending => println!("Pending..."),
}
}
// For the noop_waker, we need the futures crate or a custom implementation
mod futures {
use std::task::{Waker, RawWaker, RawWakerVTable};
pub fn noop_waker() -> Waker {
fn noop_clone(_: *const ()) -> RawWaker {
RawWaker::new(std::ptr::null(), &NOOP_VTABLE)
}
fn noop(_: *const ()) {}
static NOOP_VTABLE: RawWakerVTable = RawWakerVTable::new(
noop_clone, noop, noop, noop,
);
unsafe { Waker::from_raw(RawWaker::new(std::ptr::null(), &NOOP_VTABLE)) }
}
}Pin Projection
use std::pin::Pin;
use std::marker::PhantomPinned;
struct PinnedStruct {
field1: String,
field2: i32,
_marker: PhantomPinned,
}
impl PinnedStruct {
fn new(s: &str, n: i32) -> Pin<Box<Self>> {
Pin::new(Box::new(Self {
field1: s.to_string(),
field2: n,
_marker: PhantomPinned,
}))
}
// Pin projection: project Pin<&mut Self> to Pin<&mut String>
// This is safe because field1 is structurally pinned
fn field1_pin(self: Pin<&mut Self>) -> Pin<&mut String> {
unsafe { self.map_unchecked_mut(|s| &mut s.field1) }
}
// field2 doesn't need pin projection since i32: Unpin
fn field2_mut(self: Pin<&mut Self>) -> &mut i32 {
unsafe { &mut self.get_unchecked_mut().field2 }
}
fn field1(&self) -> &String {
&self.field1
}
}
fn main() {
let mut pinned = PinnedStruct::new("hello", 42);
println!("field1: {}", pinned.field1());
println!("field2: {}", pinned.field2_mut());
// Modify fields through pinned access
pinned.as_mut().field1_pin().push_str(" world");
*pinned.as_mut().field2_mut() = 100;
println!("After modification:");
println!("field1: {}", pinned.field1());
println!("field2: {}", pinned.field2_mut());
}Using pin_project Crate (Recommended)
// This example shows how pin_project simplifies pin projections
// In Cargo.toml: pin_project = "1"
/*
use pin_project::pin_project;
#[pin_project]
struct MyStruct {
#[pin]
pinned_field: String, // This field is pinned
unpinned_field: i32, // This field is not pinned
}
impl MyStruct {
fn new() -> Pin<Box<Self>> {
Pin::new(Box::new(Self {
pinned_field: String::from("pinned"),
unpinned_field: 42,
}))
}
fn modify(self: Pin<&mut Self>) {
let this = self.project();
// pinned_field is Pin<&mut String>
this.pinned_field.push_str(" data");
// unpinned_field is &mut i32
*this.unpinned_field = 100;
}
}
fn main() {
let mut pinned = MyStruct::new();
pinned.as_mut().modify();
}
*/
// Manual example without pin_project:
use std::pin::Pin;
#[derive(Debug)]
struct MyStruct {
pinned_field: String,
unpinned_field: i32,
}
impl MyStruct {
fn new() -> Pin<Box<Self>> {
Pin::new(Box::new(Self {
pinned_field: String::from("pinned"),
unpinned_field: 42,
}))
}
}
fn main() {
let pinned = MyStruct::new();
println!("{:?}", *pinned);
}Stack Pinning
use std::pin::Pin;
fn main() {
// Pinning on the stack (requires unsafe to use correctly)
let mut value = 42;
// Create a pinned reference
let pinned = unsafe { Pin::new_unchecked(&mut value) };
// For Unpin types, this is safe
let reference = pinned.get_mut();
*reference = 100;
println!("Value: {}", value);
// For !Unpin types, stack pinning is dangerous
// because the value could be moved after pinning
}Box::pin for Heap Allocation
use std::pin::Pin;
struct MyFuture {
state: i32,
}
impl MyFuture {
fn new() -> Pin<Box<Self>> {
Box::pin(Self { state: 0 })
}
}
fn main() {
// Box::pin is the most common way to create pinned values
let pinned: Pin<Box<MyFuture>> = MyFuture::new();
// Common in async runtimes
let future = async {
42
};
let pinned_future = Box::pin(future);
println!("Future pinned");
}Pin with async/await
use std::pin::Pin;
use std::future::Future;
// Understanding how async generates !Unpin futures
async fn example_async() -> i32 {
let data = String::from("hello");
// This await point creates a state machine that may be !Unpin
// because it needs to store references across yield points
some_async_operation().await;
42
}
async fn some_async_operation() {
// Simulated async work
}
fn main() {
// The async block creates an anonymous type that is !Unpin
let future = example_async();
// Futures must be pinned before polling
let mut pinned = Box::pin(future);
println!("Future created and pinned");
}Common Pin Methods
use std::pin::Pin;
fn main() {
let boxed = Box::new(42i32);
let pinned: Pin<Box<i32>> = Pin::new(boxed);
// as_ref: Pin<&T> from Pin<Box<T>>
let pin_ref: Pin<&i32> = pinned.as_ref();
println!("Ref: {}", *pin_ref);
let mut pinned = Pin::new(Box::new(42i32));
// as_mut: Pin<&mut T> from Pin<Box<T>>
let pin_mut: Pin<&mut i32> = pinned.as_mut();
// get_mut: &mut T (only for Unpin types)
let reference = pin_mut.get_mut();
*reference = 100;
// into_inner: Unwrap to get T (for Unpin) or Box<T>
let unpinned: Box<i32> = pinned.into_inner();
println!("Unpinned: {}", unpinned);
// set: Replace the value
let mut pinned = Pin::new(Box::new(42i32));
let old_value = pinned.as_mut().set(200);
println!("Old: {:?}, New: {}", old_value, *pinned);
}Pin Safety Rules
use std::pin::Pin;
use std::marker::PhantomPinned;
// A type that opts out of Unpin
struct NotUnpin {
_marker: PhantomPinned,
}
fn main() {
let boxed = Box::new(NotUnpin { _marker: PhantomPinned });
let pinned = Pin::new(boxed);
// Cannot call get_mut() because NotUnpin is !Unpin
// let reference = pinned.get_mut(); // Compile error!
// Cannot call into_inner() because NotUnpin is !Unpin
// let unpinned = pinned.into_inner(); // Compile error!
// To access a !Unpin type, you must use unsafe
let mut pinned = pinned;
unsafe {
let reference = pinned.as_mut().get_unchecked_mut();
// You must ensure the value is not moved!
let _ref = reference;
}
println!("Pinned value exists");
}Implementing a Simple Future
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
use std::marker::PhantomPinned;
struct CountingFuture {
count: usize,
target: usize,
_marker: PhantomPinned, // Make it !Unpin
}
impl CountingFuture {
fn new(target: usize) -> Self {
Self {
count: 0,
target,
_marker: PhantomPinned,
}
}
}
impl Future for CountingFuture {
type Output = usize;
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safe because we don't move any pinned data
let this = unsafe { self.get_unchecked_mut() };
this.count += 1;
if this.count >= this.target {
Poll::Ready(this.count)
} else {
println!("Count: {}", this.count);
Poll::Pending
}
}
}
// A simple executor
fn block_on<F: Future>(mut future: F) -> F::Output {
use std::task::{RawWaker, RawWakerVTable, Waker};
let vtable = RawWakerVTable::new(
|_| RawWaker::new(std::ptr::null(), &VTABLE),
|_| {},
|_| {},
|_| {},
);
static VTABLE: RawWakerVTable = vtable;
let raw = RawWaker::new(std::ptr::null(), &VTABLE);
let waker = unsafe { Waker::from_raw(raw) };
let mut cx = Context::from_waker(&waker);
let mut future = unsafe { Pin::new_unchecked(&mut future) };
loop {
match future.as_mut().poll(&mut cx) {
Poll::Ready(val) => return val,
Poll::Pending => std::thread::yield_now(),
}
}
}
fn main() {
let future = CountingFuture::new(5);
let result = block_on(future);
println!("Final result: {}", result);
}Summary
Pin Types:
| Type | Description |
|---|---|
Pin<P> |
Wrapper preventing the pointee from moving |
Pin<Box<T>> |
Pinned heap allocation |
Pin<&mut T> |
Pinned mutable reference |
Pin<&T> |
Pinned shared reference |
Key Traits:
| Trait | Meaning |
|---|---|
Unpin |
Type can be moved even when pinned (auto-implemented for most types) |
!Unpin |
Type must not be moved once pinned (requires PhantomPinned) |
Key Methods:
| Method | Unpin Required? | Description |
|---|---|---|
new(p) |
No | Create Pin (for pointers) |
new_unchecked(p) |
No | Create Pin (unsafe, for references) |
get_mut() |
Yes | Get mutable reference |
get_unchecked_mut() |
No (unsafe) | Get mutable reference |
into_inner() |
Yes | Unwrap to inner pointer |
set(value) |
No | Replace the value |
as_ref() |
No | Get Pin<&T> |
as_mut() |
No | Get Pin<&mut T> |
When to Use Pin:
| Scenario | Pin Required? |
|---|---|
| Working with async/await futures | ā Yes (implicitly) |
| Self-referential structs | ā Yes |
| Normal structs | ā Not needed (Unpin) |
| Simple references | ā Not needed |
Key Points:
Pin<P>prevents the pointed-to value from being moved- Most types are
Unpinand can be moved freely - Futures generated by async/await are typically
!Unpin PhantomPinnedmarker opts a type out ofUnpin- Stack pinning requires
unsafeand careful attention Box::pin()is the safest way to create pinned values- Use
pin_projectcrate for safe pin projection in structs - Pin is fundamental to understanding Rust's async runtime
