vrongmeal

What is std::pin::Pin in Rust?

std::pin::Pin is a pointer wrapper that represents the guarantee that the pointee will not be moved through that pointer.

Why is it required?

The need for pinning arises from self-referential types. Self-referential structs are the most common example of address-sensitive types, and are the primary motivation for Pin. Take the following struct for example:

1struct SelfRef {
2    data: i32,
3    ptr: *const i32, // points to self.data
4}

If we move an instance of this struct (for example, by moving ownership into another variable or returning it), its memory address changes. However, the raw pointer ptr still refers to the old memory location, leaving a dangling pointer. Hence, we need a way to prevent moving SelfRef once those self-references have been established.

Where do we see this problem?

We see this most commonly with async/await and Futures.

Local variables that live across an .await point become fields in the compiler-generated state machine. If a reference to one local variable also lives across the same .await, the generated future becomes self-referential.

Once polling begins, the future may rely on internal references that point to other fields within itself. Moving the future afterward would invalidate those references. To prevent this, the Future::poll method requires the future to be pinned:

1pub trait Future {
2    type Output;
3    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
4}

By taking Pin<&mut Self> instead of &mut self, callers of poll are required to guarantee that the future will not be moved after polling begins.

How does pinning work?

Pin<P> prevents safe code from moving the pointee through that pointer while still allowing ordinary mutation of the pinned value.

The Problem with &mut T

If you have a mutable reference &mut T, functions like mem::replace, mem::swap, or assignment can relocate the value stored at that memory location.

Pin restricts recovering an ordinary mutable reference. Safe code cannot recover an ordinary &mut T from a Pin<&mut T> unless T: Unpin.

 1impl<'a, T: ?Sized> Pin<&'a T> {
 2    pub const fn get_ref(self) -> &'a T { ... }
 3}
 4
 5impl<'a, T: ?Sized> Pin<&'a mut T> {
 6    pub const fn get_mut(self) -> &'a mut T
 7    where
 8        T: Unpin
 9    { ... }
10}

Important

Pin only prevents moves through the pinned pointer; it does not prevent mutation of the pinned value. Methods on the pinned type may freely mutate its fields, provided they do not move them.

If the type does not implement Unpin (i.e., it is !Unpin), you cannot get &mut T using safe code. You must use unsafe methods like Pin::get_unchecked_mut, indicating to the compiler that you promise not to move the value out of that reference.

What is Unpin?

A type implementing Unpin does not rely on pinning for soundness.

1// std::marker
2pub auto trait Unpin {}

Most types in Rust (like i32, String, Vec, etc.) do not care about being moved and are Unpin by default. Unpin is implemented for all types automatically unless !Unpin is explicitly implemented.

Tip

The marker struct std::marker::PhantomPinned is explicitly !Unpin. Because auto-traits propagate automatically, any struct containing a PhantomPinned field is also automatically !Unpin.

1use std::marker::PhantomPinned;
2
3struct SelfRef {
4    data: i32,
5    ptr: *const i32,
6    _phantom: PhantomPinned, // makes the entire struct !Unpin
7}

This is the standard way to declare that a custom struct is unsafe to move after being pinned.

Because the compiler cannot automatically detect self-references (which are typically created using unsafe raw pointers), it cannot automatically mark such structs as !Unpin.

Therefore, this relies on a contract: the developer must explicitly opt out of Unpin (typically by embedding a PhantomPinned field) for any self-referential struct. If a self-referential type mistakenly remains Unpin, safe code can recover a mutable reference from a Pin and move the value, violating the assumptions made by the unsafe code that created the self-reference.

Pin does not physically prevent values from moving. Instead, it is a type-level guarantee that the value will not be moved through that pointer. Constructing a Pin safely therefore requires ensuring that the pointee will remain at a stable memory location for the lifetime of that pin.

Constructing a Pin

Pin itself does not pin a value. Instead, constructing a Pin means proving that the pointee will remain at a stable memory location for the lifetime of that pin.

Pin::new

The simplest way to construct a Pin is with Pin::new:

1let mut value = 42;
2let pinned = Pin::new(&mut value);

However, this constructor is only available when T: Unpin.

Since Unpin types do not rely on pinning for soundness, wrapping them in a Pin is always safe. The pinning guarantee is effectively a no-op.

pin!

When you need to pin a value locally without allocating on the heap, you can use the pin! macro:

1use std::pin::pin;
2
3let future = pin!(async {
4	println!("Hello");
5});

The macro creates a local variable and returns a Pin<&mut T> referring to it. The compiler ensures that the local variable will not be moved for the remainder of its lifetime, making this a safe way to pin !Unpin values on the stack.

Warning

Despite the name, pin! does not pin the stack memory itself. It creates a pinned reference whose lifetime is tied to the local variable. Once the variable goes out of scope, the pinning guarantee ends.

Box::pin

For !Unpin types, the most common constructor is Box::pin:

1let pinned = Box::pin(SelfRef { ... });

Unlike pin!, which creates a Pin<&mut T> tied to a local variable, Box::pin returns a Pin<Box<T>> whose lifetime is owned by the Box. Because the heap allocation itself does not move, the pointee has a stable memory location for the lifetime of the Box, allowing it to be safely pinned.

Note

Moving the Box itself does not move the value it owns. Only the pointer stored inside the Box is moved; the heap allocation remains at the same address.

Pin::new_unchecked

Sometimes safe constructors cannot prove that a value will remain in place. In these cases, unsafe code can construct a Pin manually:

1let pinned = unsafe { Pin::new_unchecked(ptr) };

By calling Pin::new_unchecked, the caller promises that the pointee will never again be moved through any pointer for the lifetime of the resulting Pin. If this promise is violated, any code relying on the pinning guarantee may exhibit undefined behavior. Because of this, Pin::new_unchecked is typically used only when implementing low-level abstractions that can uphold this invariant.

When do you actually need to care?

For most Rust developers, Pin and Unpin operate quietly in the background. You generally only need to think about them in two scenarios:

  1. Consuming async code: If you need to manually poll a future or pass a future to an API that requires a pinned future, reach for Box::pin(future) (to pin it on the heap) or std::pin::pin!(future) (to pin it locally on the stack).
  2. Implementing Future by hand: If you’re writing a custom state machine or another low-level async primitive, you’ll need to work with Pin<&mut Self> and may need to use PhantomPinned and unsafe code to uphold pinning invariants.

Ultimately, Pin is Rust’s zero-cost solution to the problem of address-sensitive types. It enables ergonomic async/await and other self-referential abstractions while preserving Rust’s memory safety guarantees without requiring a garbage collector.