---
title: How to Use Pin and Unpin Correctly in Async Rust
summary: Use `std::pin::pin!` to pin a value when you need to borrow it mutably across `.await` points in an async function. Pinning prevents the value from moving in memory, which is required for self-referential types or when using `Pin<&mut T>` in async runtimes.
lang: en
slug: how-to-use-pin-and-unpin-correctly-in-async-rust
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-pin-and-unpin-correctly-in-async-rust/
---

# How to Use Pin and Unpin Correctly in Async Rust



## The moving target problem

You're building an async state machine. You maintain a buffer and a pointer into that buffer. You call `.await` to fetch the next chunk of data. The runtime suspends the future, moves the entire state machine to a new memory location, and resumes it later. The buffer moved. The pointer now points to garbage. Your program reads random memory and crashes.

This is the core problem `Pin` solves. `Pin` prevents values from moving in memory, ensuring that internal pointers remain valid across suspension points. Without pinning, self-referential types are impossible to use safely in async code.

## Pinning is a contract, not a lock

`Pin` is not a mutex. It does not control access. It controls movement. When you wrap a pointer in `Pin`, you are making a guarantee: the value behind this pointer will not be moved. The compiler enforces this guarantee by restricting how you can interact with the value. You cannot extract a mutable reference. You cannot move the value out of the `Pin`. You can only access the value through `Pin`-aware methods.

Think of a value on the stack like a tent. You can pick up the tent and move it anywhere. `Pin` is like driving a stake through the tent into the ground. Once staked, the tent stays put. You can't move the tent without pulling the stake. The `Pin` wrapper is the stake.

The type `Pin<P>` wraps a pointer `P`. The pointer can be a reference `&mut T` or a box `Box<T>`. The `Pin` wrapper doesn't change the pointer itself. It changes what you can do with the pointee. If the pointee is pinned, the compiler forbids operations that would move it.

## Unpin: the opt-out

Most types do not care if they move. A `Vec`, a `String`, an `i32`. These types implement the `Unpin` trait. `Unpin` is a marker trait that tells the compiler, "This type is safe to move." When a type implements `Unpin`, `Pin` becomes a no-op. `Pin<&mut T>` is effectively the same as `&mut T`. You can unwrap it, move the value, and the compiler allows it.

`Unpin` is an auto-trait. If your type contains only `Unpin` fields, it automatically implements `Unpin`. You rarely need to write `impl Unpin` manually. The compiler does the work for you.

Types that hold pointers to their own interior cannot implement `Unpin`. If a struct contains a `*const u8` pointing into its own `Vec<u8>`, moving the struct invalidates the pointer. Such types must opt out of `Unpin`. The compiler prevents `Unpin` implementation for types with raw pointers or `Pin` fields by default. This is a safety feature. If a type could move and break its own pointers, it must be pinned.

Convention aside: The community treats `Unpin` as the default. If you are writing a type and you don't have self-references, you don't touch `Unpin`. If you have self-references, you ensure `Unpin` is not implemented. You never implement `Unpin` for a self-referential type. Doing so breaks the safety contract.

## Minimal example: pinning on the stack

Pinning a value on the stack uses the `pin!` macro. This is the idiomatic way to pin local variables. The macro allocates the value and wraps it in `Pin`. You cannot move the value out of the macro.

```rust
use std::pin::pin;

fn main() {
    // The pin! macro pins the future on the stack.
    // The future cannot be moved after this point.
    let mut future = pin!(async { 42 });

    // You cannot extract the future.
    // This line would fail to compile.
    // let _extracted = future.into_inner();

    // You can poll the future through the Pin wrapper.
    // The runtime uses Pin<&mut T> to poll futures.
    // This guarantees the future won't move during polling.
}
```

For heap-allocated values, you use `Box::pin`. This allocates the value on the heap and pins it there. The `Box` can move, but the value inside the `Box` cannot. This is essential for async runtimes. The runtime moves the `Box` around, but the future inside stays fixed in memory.

```rust
fn main() {
    // Box::pin allocates and pins in one step.
    // This is the standard way to create pinned futures.
    let pinned_future = Box::pin(async { 42 });

    // The Box can be moved, but the future inside is pinned.
    let _moved_box = pinned_future;
}
```

Convention aside: Always use `Box::pin` for futures. Never use `Box::new` for a future you intend to poll. `Box::new` gives you a `Box<T>`, which is not pinned. You would need to wrap it in `Pin` manually, which is error-prone. `Box::pin` is the safe, standard choice.

## How Pin enforces immobility

The compiler enforces pinning through the API. `Pin` provides methods that respect the pinning contract. `Pin::as_mut` gives you a `Pin<&mut T>` from a `&mut Pin<T>`. This is a projection. It lets you pass a pinned reference to functions that expect `Pin<&mut T>`.

If the type implements `Unpin`, `Pin` provides `get_mut`. This returns a `&mut T`. You can modify the value freely. The compiler knows the value can move, so it allows the mutable reference.

If the type does not implement `Unpin`, `get_mut` is unavailable. You cannot get a `&mut T`. The only way to access the value is `get_unchecked_mut`, which is `unsafe`. This method returns a `&mut T` without checking anything. The caller must guarantee that the value is pinned and will not move. This is where the safety boundary sits. You use `unsafe` to access the interior, but you must prove the pinning holds.

```rust
use std::pin::Pin;

fn access_pinned<T>(pinned: Pin<&mut T>) {
    // If T is Unpin, get_mut is available.
    // If T is not Unpin, this line won't compile.
    // let _ref = pinned.get_mut();

    // get_unchecked_mut is always available, but unsafe.
    // You must ensure the value is pinned.
    // let _ref = unsafe { pinned.get_unchecked_mut() };
}
```

Pinning is a promise, not a shield. `Pin` does not make raw pointers safe. You still need `unsafe` to dereference them. `Pin` only ensures the address is stable. If you dereference a raw pointer inside a pinned value, you must verify the pointer is valid. The pinning guarantee helps, but it doesn't replace pointer safety checks.

## Realistic example: a self-referential future

Self-referential types are rare in safe Rust, but they appear in async code. Futures are state machines. The compiler generates a struct with fields for the state and local variables. If the future holds a pointer to its own buffer, it is self-referential. The `Future` trait requires `Pin<&mut Self>` in the `poll` method. This ensures the future is pinned when polled.

```rust
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

struct SelfRefFuture {
    buffer: Vec<u8>,
    // This pointer points into buffer.
    // Moving SelfRefFuture would invalidate cursor.
    cursor: *const u8,
}

// SAFETY: SelfRefFuture is self-referential.
// It must not move while cursor is valid.
// We do not implement Unpin to enforce this.

impl Future for SelfRefFuture {
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        // SAFETY: We access cursor only through Pin.
        // Pin guarantees buffer won't move, so cursor stays valid.
        // We must not move SelfRefFuture after this access.
        let this = unsafe { self.get_unchecked_mut() };

        // Access buffer via cursor.
        // This is safe because Pin ensures immobility.
        let _val = unsafe { *this.cursor };

        Poll::Ready(())
    }
}
```

The `poll` method takes `Pin<&mut Self>`. This is the contract. The runtime pins the future before polling. You can rely on the future not moving during the poll. If you need to access self-referential fields, you use `get_unchecked_mut` inside `unsafe`. The `SAFETY` comment documents the invariant: the value is pinned, so internal pointers remain valid.

Treat the `SAFETY` comment as a proof. If you can't write the invariant, you don't have one.

## Pin projections

Real async code often needs to project `Pin` through fields. If you have `Pin<&mut Outer>`, you might need `Pin<&mut Inner>` for a field. This is called pin projection. You cannot simply take a reference to the field. The field might not be pinned. You must project the `Pin` wrapper.

Manual pin projections are tedious. You write a `project` method that returns pinned references to all fields. The community uses the `pin-project` crate to automate this. The crate generates the projection code for you. It handles the `Unpin` bounds correctly.

Convention aside: Use `pin-project` for any struct that implements `Future` or contains `Pin` fields. Writing manual projections is error-prone. The crate is the standard tool. It adds a small dependency, but it saves bugs.

## Pitfalls and compiler errors

You try to extract a value from `Pin`. You call `into_inner()`. The compiler rejects you with `E0509` (cannot move out of type which does not implement Unpin). This error means the type is pinned and cannot be moved. You must drop the `Pin` or use `Unpin` to extract the value.

You try to get a mutable reference. You write `&mut *pinned`. The compiler rejects this. You cannot dereference `Pin` to get a mutable reference. You must use `as_mut()` or `get_unchecked_mut()`. The compiler enforces the pinning contract at the type level.

You assume `Pin` makes everything safe. `Pin` does not validate pointers. It only prevents movement. If you have a dangling pointer inside a pinned value, dereferencing it is still undefined behavior. `Pin` helps by ensuring the pointer doesn't become dangling due to movement, but it doesn't fix existing dangles.

You forget that `Pin` is not `Copy`. `Pin<&mut T>` is not `Copy`. You cannot clone a pinned mutable reference. You must use `as_mut()` to create a new `Pin<&mut T>` from a `&mut Pin<T>`. This is a projection, not a clone.

## When to use Pin

Use `Pin<&mut T>` when you are implementing a self-referential type and need to guarantee the memory address stays constant across borrows. Use `Box::pin` when you need to allocate a future on the heap and pass it to an executor; this pins the future so the runtime can move the `Box` without moving the future inside. Use the `pin!` macro when you have a local variable that must be pinned on the stack, such as a future you are polling manually. Use `Unpin` marker trait when you are defining a type that contains no self-references and want to allow the compiler to optimize away `Pin` overhead. Reach for `RefCell` or interior mutability when you need to mutate data behind an immutable reference; `Pin` does not provide mutability, it only provides immobility.

Trust the borrow checker. It usually has a point. If the compiler complains about `Pin`, check your self-references. If you don't have self-references, you probably don't need `Pin`.

## Where to go next

- [What is the difference between tokio and async-std](/en/what-is-the-difference-between-tokio-and-async-std/)
- [How to use async with file IO in Rust](/en/how-to-use-async-with-file-io-in-rust/)
- [How to Implement Async Drop in Rust](/en/how-to-implement-async-drop-in-rust/)