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
  • Unpin trait — 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 Future trait 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 Unpin and can be moved freely
  • Futures generated by async/await are typically !Unpin
  • PhantomPinned marker opts a type out of Unpin
  • Stack pinning requires unsafe and careful attention
  • Box::pin() is the safest way to create pinned values
  • Use pin_project crate for safe pin projection in structs
  • Pin is fundamental to understanding Rust's async runtime