The Future trait receives self: Pin<&mut Self> when it is polled. To
understand why, first consider a value whose correctness depends on its address.
Which moves can invalidate a pointer?
Suppose a struct contains an inline array and a raw pointer to one of that array's elements. Moving the struct to another address relocates the array, but does not automatically update the stored pointer. An API that later dereferences that pointer must prevent this situation or repair the pointer.
Moving a value does not necessarily move everything it owns. A String or
Vec stores its elements in a separate allocation. Moving its header transfers
ownership of that allocation without relocating the elements. A pointer into
the buffer does not become invalid solely because the header moved, though
reallocation or destruction can invalidate it.
This distinction matters: swapping two structs, each containing a String
and a pointer into its own string buffer, does not by itself break those
associations. The pointer and owning header move together.
Pinning addresses the case where the pointee's own address must stay fixed. It does not automatically make raw pointers valid or prevent a nested buffer from reallocating.
Why polling a future can require a stable address
An async function may retain a reference to one of its own stored locals across a suspension point. An inline array makes the concern clear:
async fn wait_with_borrow() {
let bytes = [7_u8, 8, 9];
let first = &bytes[0];
std::future::pending::<()>().await;
std::hint::black_box(first);
}
This future never completes because pending() never becomes ready. The
example shows a borrow needed after an await, rather than a useful operation.
A suspended state machine may need to retain both the array and a reference
into it. Moving that state could invalidate the reference.
Calling an async function constructs its future without running the body.
The returned future can be moved before it is pinned and polled. Once it is
pinned, code must preserve the pinning contract even when no poll is active.
Not every future is self-referential; the common poll interface must support
those that are.
Pin protects the pointee
Pin<Ptr> wraps a pointer such as &mut T or Box<T>. For an address-sensitive
target, it represents a promise that the target will stay at its address until
it is dropped. Moving the pointer wrapper is different from moving its target.
This complete example moves a pinned box while its allocation stays put:
use std::marker::PhantomPinned;
struct Stationary {
byte: u8,
_pin: PhantomPinned,
}
fn main() {
let pinned = Box::pin(Stationary {
byte: 7,
_pin: PhantomPinned,
});
let before = std::ptr::from_ref(&pinned.as_ref().get_ref().byte);
let moved_box = pinned;
let after = std::ptr::from_ref(&moved_box.as_ref().get_ref().byte);
assert_eq!(before, after);
}
PhantomPinned prevents automatic Unpin implementation. It does not pin a
value by itself: an ordinary unpinned Stationary can still be moved.
Unpin allows ordinary mutable access
Unpin means a type does not require Pin's address-stability restrictions.
It is an auto trait, so ordinary structs generally receive it when their
fields do. Types may also implement it explicitly when their invariants allow
that. Compiler-generated async futures generally require pinning to use this
interface; do not infer their Unpin status just by inspecting their locals.
For an Unpin target, safe methods can recover an ordinary mutable reference:
use std::pin::Pin;
fn main() {
let mut number = 7;
let pinned = Pin::new(&mut number);
*pinned.get_mut() = 8;
assert_eq!(number, 8);
}
For a target that is not Unpin, Pin<&mut T> does not generally expose
&mut T. Such a reference would permit moving the value with operations like
mem::replace. Shared access remains available, as do methods designed to
receive Pin<&mut Self> or Pin<&Self>.
Pinning does not mean immutable data. A type can offer mutations that preserve
its address-sensitive invariants. Pin::set can even replace a pinned value
by first dropping the old value in place, then installing the replacement.
Choose a safe constructor
- Use
std::pin::pin!for local pinned storage. Inside an async function, that storage can be part of the enclosing future rather than a literal stack slot. - Use
Box::pinwhen you want owned heap storage that can be returned or kept in another object. - Use
Pin::newfor a pointer whose target implementsUnpin.
For example, polling a local async future once can look like this:
use std::future::Future;
use std::pin::pin;
use std::task::{Context, Waker};
fn main() {
let future = async { 7 };
let mut future = pin!(future);
let mut context = Context::from_waker(Waker::noop());
assert!(future.as_mut().poll(&mut context).is_ready());
}
Moving a Pin<Box<F>> field does not move its F allocation. This often
makes a wrapper easier to implement, but whether the entire wrapper is
Unpin still depends on its other fields and invariants.
Unsafe code and field access must preserve the contract
Pin is a library abstraction whose safe interface relies on contracts upheld
by its constructors, pointer types and unsafe implementations. Restricting
DerefMut is an important part of that interface, not the entire safety argument.
Merely defining fn into_inner(self) on a type does not let callers move it
out of a pinned reference: they still need an owned self to call that method.
The danger is an API or unsafe implementation that actually exposes a forbidden
move, such as obtaining &mut Self from a pinned receiver and replacing a
field that was promised to remain pinned.
Projecting Pin<&mut Parent> to Pin<&mut Field> makes that promise for the
field. The parent's Unpin implementation, destructor and mutation methods
must respect it. Packed fields can introduce alignment and destructor moves,
so they cannot be treated as ordinary structurally pinned fields.
Pin::new_unchecked is for code that can justify these obligations. It is not
a way to silence a pinning error. Its contract continues after the temporary
Pin wrapper has gone away.
Keep storage valid through destruction
A pinned value's storage must stay valid until its destructor has run. For example, an intrusive list node may need its destructor to remove pointers that other nodes hold to it before its storage disappears.
Safe mem::forget is still permitted. Forgetting a pinned box leaks its
allocation, so it does not move or invalidate the target. Forgetting a
borrowed Pin wrapper does not give permission to reuse the borrowed storage
in violation of an earlier unsafe pinning promise.
Pin does not guarantee that destructors always execute. It requires that storage not be invalidated before the required in-place destruction. Leaking storage and freeing it without running the necessary destructor are different.
Practice ordinary pinned access in Unpin in practice, then examine field projection in Pin projection by hand.