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>implementsDerefMut<Target = T>, so you can obtain&mut Tdirectly (via&mut *pinnedorpinned.get_mut()) and even callstd::mem::swaporstd::mem::replaceon 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
Unpinif and only if all of its fields implementUnpin. - If a struct contains a compiler-generated
asyncblock future or astd::marker::PhantomPinnedfield, the enclosing struct automatically becomes!Unpin. - Importantly,
&'a T,&'a mut T,Box<T>, andPin<P>always implementUnpineven whenT: !Unpin, because moving a pointer or reference on the stack only moves the pointer address itself, not the heap/stack targetTit 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> {}.