Advanced Lifetimes and Borrowing

Unpin

std::marker::Unpin is a safe auto-trait that marks types whose values can be safely moved even after being placed inside Pin<P>. Nearly every normal type in Rust—including all primitives (i32, bool), references (&T, &mut T), standard collections (Vec<T>, String, HashMap<K, V>), and pointers (Box<T>, Pin<P>)—implements Unpin automatically because none of them store internal self-referential pointers.

How Unpin Makes Pin<P> Transparent

When T: Unpin, pinning has no restrictions on moving T:

  • Pin::new(&mut val) is 100% safe to call.
  • Pin<&mut T> implements DerefMut<Target = T>, so you can obtain &mut T directly (via &mut *pinned or pinned.get_mut()) and even call std::mem::swap or std::mem::replace on the inner value.
Does `T` implement `Unpin`?
  |
  +-- YES (`T: Unpin` — Almost all standard Rust types)
  |     - `Pin::new(&mut val)` is safe
  |     - `Pin<&mut T>` implements `DerefMut` -> yields `&mut T` freely
  |     - Moving `T` out of `Pin<&mut T>` is safe
  |
  +-- NO (`T: !Unpin` — Compiler-generated `async` futures & `PhantomPinned` structs)
        - `Pin::new(&mut val)` fails to compile (must use `pin!(val)` or `Box::pin(val)`)
        - `Pin<&mut T>` does NOT implement `DerefMut`
        - Obtaining `&mut T` requires `unsafe { pinned.get_unchecked_mut() }`
use std::pin::Pin;

fn main() {
    let mut a = String::from("alpha");
    let mut b = String::from("beta");

    // Because `String: Unpin`, `Pin::new` and `DerefMut` work in safe Rust:
    let mut pin_a: Pin<&mut String> = Pin::new(&mut a);
    let mut pin_b: Pin<&mut String> = Pin::new(&mut b);

    std::mem::swap(&mut *pin_a, &mut *pin_b);
    println!("a = {:?}, b = {:?}", a, b);
}
a = "beta", b = "alpha"

Auto-Trait Propagation Rules for Unpin

Like Send and Sync, Unpin is an auto-trait:

  • A struct or enum automatically implements Unpin if and only if all of its fields implement Unpin.
  • If a struct contains a compiler-generated async block future or a std::marker::PhantomPinned field, the enclosing struct automatically becomes !Unpin.
  • Importantly, &'a T, &'a mut T, Box<T>, and Pin<P> always implement Unpin even when T: !Unpin, because moving a pointer or reference on the stack only moves the pointer address itself, not the heap/stack target T it points to!
Type Implements Unpin? Reason
i32, String, Vec<T> Yes Contains no self-referential state
PhantomPinned No (!Unpin) Explicit opt-out marker in std::marker
async { ... } block / async fn future No (!Unpin) State machine may hold references across .await points
Box<T> (where T: !Unpin) Yes Moving the Box pointer does not move the heap allocation T
Pin<Box<T>> / Pin<&mut T> (where T: !Unpin) Yes Moving the Pin handle itself leaves the pinned pointee T in place

Implementing Unpin Manually for a Wrapper Type

Because Unpin is a safe trait (pub auto trait Unpin {}—not an unsafe trait), if a wrapper struct contains a !Unpin field inside a heap allocation (Box<T>) or never projects pinning to that field, it can safely opt back into Unpin with impl<T> Unpin for MyWrapper<T> {}.