Unpin
The Unpin trait in std::marker is an auto trait indicating that a value can be safely moved in memory even after being wrapped in a Pin<P> pointer.
Trait Definition
pub auto trait Unpin {}
Most types in Rust have no internal pointers into their own fields, so moving them from one memory address to another with memcpy is always safe. Because Unpin is an auto trait, almost every standard type (i32, String, Vec<T>, Box<T>, references) implements Unpin automatically.
Why Pin<P> and Unpin Exist
Compiler-generated async state machines can hold references to their own local variables across .await points, making them self-referential structs. Moving a self-referential struct in memory invalidates its internal pointer:
Before Move (at 0x1000) After Move to 0x2000 (Invalid!)
+-----------------------+ +-----------------------+
| data: "hi" (0x1000) | <---+ | data: "hi" (0x2000) |
| ptr: 0x1000 | ----+ | ptr: 0x1000 (dangling)| ──> 0x1000
+-----------------------+ +-----------------------+
Pin<P> prevents obtaining a &mut T to the pinned value unless T: Unpin.
Behavior of Pin<&mut T> Based on Unpin
Does T Implement Unpin? |
Examples | Effect of Pin<&mut T> |
|---|---|---|
Yes (T: Unpin) |
i32, String, Vec<T>, most structs |
Pin<&mut T> implements DerefMut<Target = T>; Pin::new(&mut val) and Pin::into_inner(pin) work in safe code |
No (!Unpin) |
async blocks/futures, PhantomPinned |
Pin<&mut T> withholds safe &mut T access so the caller cannot call std::mem::swap or std::mem::replace to move T |
Opting Out of Unpin with PhantomPinned
To mark a custom struct as !Unpin, include a std::marker::PhantomPinned field:
use std::marker::PhantomPinned;
use std::pin::Pin;
struct NormalBuffer {
data: String,
}
struct PinnedNode {
label: String,
_pin: PhantomPinned, // Makes PinnedNode !Unpin
}
fn main() {
// 1. NormalBuffer is Unpin -> Pin::new works in safe code and allows DerefMut:
let mut normal = NormalBuffer {
data: String::from("movable"),
};
let mut pinned_normal = Pin::new(&mut normal);
pinned_normal.data.push_str(" (mutated)");
println!("NormalBuffer: {}", pinned_normal.data);
// 2. PinnedNode is !Unpin -> must be pinned on the heap via Box::pin:
let node: Pin<Box<PinnedNode>> = Box::pin(PinnedNode {
label: String::from("address-stable"),
_pin: PhantomPinned,
});
println!("PinnedNode: {}", node.label);
}
NormalBuffer: movable (mutated)
PinnedNode: address-stable