Unpin
In the trait system, std::marker::Unpin is a safe auto-trait (pub auto trait Unpin {}) that governs how a type interacts with Pin<P> and trait bounds on generic wrappers, futures, and async I/O streams.
Why Unpin Is a Safe Trait (Unlike Send and Sync)
Whereas Send and Sync are unsafe traits because implementing them on a non-thread-safe type immediately permits data races in safe code, Unpin is a safe trait:
- Implementing
Unpinfor a typeT(impl Unpin for MyType {}) never causes undefined behavior by itself unless unsafe code insideMyTypepreviously relied onMyTypebeing!Unpinto create unchecked self-referential pointers. - Any safe wrapper struct that contains a
!Unpinfield—but never exposes a pinned projectionPin<&mut Field>of that field—can safely implementimpl<T> Unpin for Wrapper<T> {}without writingunsafe.
When Must a Wrapper Struct Be `!Unpin` vs. `Unpin`?
struct Wrapper<F> { inner: F }
|
+-- Does `Wrapper` project `Pin<&mut Wrapper<F>>` -> `Pin<&mut F>`?
|
+-- YES (Structural Pinning):
| `Wrapper<F>` may ONLY implement `Unpin` when `F: Unpin`!
| (`impl<F: Unpin> Unpin for Wrapper<F> {}` — the default auto-trait rule!)
|
+-- NO (Never pins `inner`, or stores `inner` inside `Box<F>`):
`Wrapper<F>` can safely implement `Unpin` unconditionally!
(`impl<F> Unpin for Wrapper<F> {}`)
Soundness Rules for Unpin and Pin Projection
If a struct StructurallyPinned<F> projects Pin<&mut Self> to Pin<&mut F> (using unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().inner) }), its Unpin implementation must require F: Unpin (which is the default auto-trait behavior). If someone manually wrote an unconditional impl<F> Unpin for StructurallyPinned<F> {} while also projecting pinning to F, safe code could pin StructurallyPinned<F>, poll the inner !Unpin future F, and then unwrap Pin<&mut StructurallyPinned<F>> via Pin::into_inner to move F in memory!
use std::future::Future;
use std::pin::{Pin, pin};
use std::task::{Context, Poll, Waker};
// Wrapper that projects `Pin<&mut Self>` to `Pin<&mut F>`.
// Because we do NOT override `Unpin`, the compiler automatically generates:
// `impl<F: Unpin> Unpin for LoggedFuture<F> {}`
struct LoggedFuture<F> {
label: &'static str,
inner: F,
}
impl<F: Future> Future for LoggedFuture<F> {
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let label = self.label;
// SAFETY: `inner` is structurally pinned because `LoggedFuture<F>` is only
// `Unpin` when `F: Unpin`, and `LoggedFuture::drop` does not move `inner`.
let inner_pinned: Pin<&mut F> = unsafe {
self.map_unchecked_mut(|s| &mut s.inner)
};
println!("[polling {:?}]", label);
inner_pinned.poll(cx)
}
}
fn main() {
let fut = LoggedFuture {
label: "task-1",
inner: async { 42 },
};
let mut pinned = pin!(fut);
let mut cx = Context::from_waker(Waker::noop());
println!("poll result: {:?}", pinned.as_mut().poll(&mut cx));
}
[polling "task-1"]
poll result: Ready(42)
Using T: Unpin Bounds in Generic Combinators
When writing generic methods that need to poll a Future or AsyncRead through a plain mutable reference &mut self (rather than self: Pin<&mut Self>), adding where F: Future + Unpin allows calling Pin::new(&mut self.future).poll(cx) in 100% safe Rust. Callers who have a !Unpin async block pass std::pin::pin!(fut) (which has type Pin<&mut Fut>, and Pin<&mut Fut> always implements Future + Unpin!) or Box::pin(fut).