Advanced Traits

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:

  1. Implementing Unpin for a type T (impl Unpin for MyType {}) never causes undefined behavior by itself unless unsafe code inside MyType previously relied on MyType being !Unpin to create unchecked self-referential pointers.
  2. Any safe wrapper struct that contains a !Unpin field—but never exposes a pinned projection Pin<&mut Field> of that field—can safely implement impl<T> Unpin for Wrapper<T> {} without writing unsafe.
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).