Skip to content

Q063. Why do Pin and Unpin exist in async Rust?

Short Answer

Some futures can contain references to their own fields after they begin execution, so moving them could invalidate those references. Pin<P> lets code promise that the pointee will not move again through that pointer. Future::poll receives Pin<&mut Self> so self-referential or address-sensitive futures can be polled safely. Unpin marks types that do not rely on this guarantee and may still be moved normally.

Deep Dive

Async state machines may store both an awaited child future and state that refers into the same parent allocation. Rust does not expose those generated self-references directly, but the polling API must support such layouts.

Pinning restricts safe access; it does not physically attach memory to the stack or heap. Box::pin(value) places a value in a heap allocation and returns Pin<Box<T>>. A stack value can also be pinned for a limited scope. Moving the pointer is fine because the pointee's address stays fixed.

Most ordinary types implement Unpin automatically. For them, Pin<&mut T> can be treated much like &mut T. Pinning matters when a type is !Unpin or when projecting pinned fields in custom future implementations.

Internal Model

Pin is a library type with compiler-supported Unpin auto-trait behavior. The address guarantee is upheld by API restrictions and unsafe-code contracts, not by runtime tracking.

The first poll is the point after which an address-sensitive future may establish internal references. Executors pin task futures before polling them.

Example

rust
use std::pin::Pin;

#[tokio::main]
async fn main() {
    let future = async {
        tokio::task::yield_now().await;
        42_u64
    };

    let pinned: Pin<Box<_>> = Box::pin(future);
    assert_eq!(pinned.await, 42);
}

Common Mistakes

  • Saying all futures are stored on the heap.
  • Believing pinning prevents moving the Box or pointer handle.
  • Using unsafe pin projection without proving that pinned fields never move.

Follow-ups

  • What does Unpin mean, and why do most types implement it?
  • Can a value be pinned on the stack?
  • Why is projecting Pin<&mut Struct> to a field non-trivial?

References

  • Rust Standard Library
  • Async Book
  • Rustonomicon