Pin & Unpin

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Rust 1.99.0, the 2024 edition and pin-project-lite 0.2, checked on 2026-10-06. Pin is the part of async Rust that most often surfaces as a confusing error — a type that cannot be unpinned, or a missing Unpin implementation. It exists to solve one problem: some values — including most futures produced by async — must not be moved in memory once they have started running. This page explains why, and shows the handful of patterns you need in practice.

The problem: self-referential values

Moving a value in Rust is a bitwise copy to a new address. That is fine for almost every type, but not for one that contains a pointer into itself: after the move, the pointer still targets the old address. Safe Rust will not let you build such a struct with references (the borrow checker rejects it), and Azam (Advanced Rust, chapter 9) shows the workarounds people try and why they fail.

An async block creates exactly such a value. Consider:

async fn first_word_len(text: String) -> usize {
    let word = text.split(' ').next().unwrap_or(""); // `word` borrows from `text`...
    tokio::task::yield_now().await;                   // ...and both live across this await point
    word.len()
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    println!("{}", first_word_len("Dune Messiah".into()).await);
}

The compiler turns this function into a state machine that stores both text and word — and word points into text, inside the same state machine. If the future were moved after it had been polled once, word would dangle. So the rule is: once a future has been polled, it must stay at the same address until it is dropped.

Pin<P> and Unpin

Pin<P> wraps a pointer P (&mut T, Box<T>, …​) and promises that the pointee will not be moved again until it is dropped. Through a Pin<&mut T> you can call methods that take self: Pin<&mut Self> — such as Future::poll — but you cannot get a plain &mut T (which would let you mem::swap the value out) unless T: Unpin.

Unpin is an auto trait meaning "this type does not care about being moved, even when pinned". Almost every type is Unpin: integers, String, Vec, Box<T>, references, and futures written by hand without self-references. The exceptions are the futures generated by async fn/async blocks and types that opt out with PhantomPinned. For an Unpin type, Pin has no effect: Pin::new(&mut x) is safe and Pin::get_mut gives the &mut back.

Situation What to do

T: Unpin (most types)

Pin::new(&mut value); pinning is a formality

!Unpin value used locally

std::pin::pin!(future) — pins it on the stack, returns Pin<&mut T>

!Unpin value that must be stored, returned or moved around

Box::pin(future) — pins it on the heap, returns Pin<Box<T>> (which is itself Unpin)

A trait object future

Pin<Box<dyn Future<Output = T> + Send>>

Source: std::pin

Stack pinning with pin! and heap pinning with Box::pin

.await pins for you, so ordinary async code never touches Pin. You need it when you poll a future by reference — most commonly in a tokio::select! loop that keeps polling the same future across iterations, or when storing futures in a collection:

use std::future::Future;
use std::pin::{Pin, pin};
use std::time::Duration;
use tokio::time::sleep;

async fn import_catalogue() -> usize {
    sleep(Duration::from_millis(120)).await;
    1_000
}

#[tokio::main]
async fn main() {
    // Stack pinning: poll the same future repeatedly by reference inside select!.
    let mut import = pin!(import_catalogue());
    let mut ticks = 0;
    let rows = loop {
        tokio::select! {
            rows = &mut import => break rows,            // needs Pin<&mut F>: hence pin!
            _ = sleep(Duration::from_millis(50)) => {
                ticks += 1;
                println!("still importing ({ticks})");
            }
        }
    };
    println!("imported {rows} rows");

    // Heap pinning: store different futures in one collection.
    let jobs: Vec<Pin<Box<dyn Future<Output = usize> + Send>>> = vec![
        Box::pin(import_catalogue()),
        Box::pin(async { 42 }),
    ];
    for job in jobs {
        println!("job returned {}", job.await);
    }
}

Source: std::pin::pin!

Without pin!, &mut import_catalogue() in select! fails with E0277: "`impl Future<Output = usize>` cannot be unpinned …​ consider using the pin! macro". pin! replaced tokio::pin! and futures::pin_mut! as the standard spelling (stable since 1.68).

Implementing Future for a wrapper: pin projection

When you write your own future that wraps another future — to add a timeout, measure latency, or log — your poll receives Pin<&mut Self> and must poll the inner future through a Pin<&mut Inner>. Getting from one to the other is called pin projection. Doing it by hand needs unsafe; the pin-project-lite crate generates the safe projection:

use pin_project_lite::pin_project;
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

pin_project! {
    /// Wraps a future and reports how long it took.
    struct Timed<F> {
        #[pin]
        inner: F,             // structurally pinned: projected as Pin<&mut F>
        started: Option<Instant>, // not pinned: projected as &mut Option<Instant>
        label: &'static str,
    }
}

impl<F: Future> Future for Timed<F> {
    type Output = F::Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<F::Output> {
        let this = self.project();
        let started = *this.started.get_or_insert_with(Instant::now);
        match this.inner.poll(cx) {
            Poll::Ready(v) => {
                println!("{} took ~{} ms", this.label, started.elapsed().as_millis() / 10 * 10);
                Poll::Ready(v)
            }
            Poll::Pending => Poll::Pending,
        }
    }
}

fn timed<F: Future>(label: &'static str, inner: F) -> Timed<F> {
    Timed { inner, started: None, label }
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let n = timed("lookup", async {
        tokio::time::sleep(Duration::from_millis(30)).await;
        7
    })
    .await;
    println!("result {n}");
}

The rule behind #[pin]: a field is structurally pinned if the wrapper, once pinned, never moves it — required for inner futures. Fields that may be moved or replaced freely are left unpinned. tower and tokio use exactly this pattern for their middleware futures (see Axum: The Tower Service Model).

Opting out of Unpin

A type that is genuinely self-referential (for example, an intrusive linked-list node) declares a PhantomPinned field, which makes it !Unpin; unsafe code can then rely on the pinning guarantee. You rarely write such types outside low-level libraries.

use std::marker::PhantomPinned;

struct NotMovable {
    _pin: PhantomPinned,
}

fn assert_unpin<T: Unpin>() {}

fn main() {
    assert_unpin::<String>();
    assert_unpin::<Box<NotMovable>>(); // the Box can move; the value inside stays put
    // assert_unpin::<NotMovable>();   // would not compile: `PhantomPinned` cannot be unpinned (E0277)
    let _x = NotMovable { _pin: PhantomPinned };
}

Common errors

Error Fix

E0277 "`...` cannot be unpinned" when polling &mut fut in select! or calling a combinator that needs Unpin

pin it: let fut = pin!(fut); (local) or Box::pin(fut) (stored)

E0308 expected Pin<Box<dyn Future<..>>>, found impl Future

wrap with Box::pin(...)

"cannot move out of dereference of `Pin<&mut ..>`"

project with pin-project-lite instead of moving fields out

E0599 no method poll found

call it through a pinned pointer: Pin::new(&mut fut).poll(cx) (only if Unpin) or fut.as_mut().poll(cx) on a Pin<&mut F>

Where the books are outdated

Azam (Advanced Rust, chapter 9) discusses self-referential structs as a motivation for Pin; the standard pin! macro (1.68) now replaces tokio::pin! and futures::pin_mut! in most code, and The Book (chapter 17.5) and the Async Book include current treatments of pinning.