Why Pin is needed for self-referential Futures
deep grasp of async internals.
async blocks compile to state machines that can hold references into their own storage; Pin guarantees the value will not move so those internal pointers stay valid across polls.
WHAT THIS TESTS It probes whether you understand how the compiler transforms async/await into state machines and why those generated types need an address-stability guarantee that ordinary Rust moves would violate.
A GOOD ANSWER COVERS When you write an async block, the compiler generates an anonymous struct implementing Future, where each await point is a state and locals that live across awaits become fields. If a local borrows another local that also lives across an await, the generated struct contains a field that points into another of its own fields; it is self-referential. Rust moves are bitwise copies of the struct, so moving it after such a pointer exists would leave the internal pointer aimed at the old, now-invalid location. Pin wraps a pointer and statically forbids obtaining &mut T for non-Unpin types, so once a future is pinned (for example by Box::pin or on the stack via pin!), it cannot be moved, and poll can safely dereference the internal pointers.
COMMON WRONG ANSWERS Saying Pin makes a value immutable; it does not, you can still mutate through it. Saying Pin is about thread safety or borrowing rules. Claiming every future is self-referential; only those with borrows across awaits are.
LIKELY FOLLOW-UPS What is the Unpin trait and why are most types Unpin? Why is poll defined to take Pin<&mut Self>? How do pin-project and Box::pin help?
ONE CONCRETE EXAMPLE Consider an async fn that creates a buffer, takes a reference into it, awaits a read that fills part of the buffer, then uses the reference. The generated future holds both the buffer and a pointer into it as fields. After the first poll suspends at the await, suppose an executor moved the future to a new address to relocate it in a task slab. The buffer's bytes move with it, but the stored pointer still holds the old address. The next poll would dereference freed or reused memory, classic undefined behavior. Pin prevents that move, so the pointer stays valid across every poll.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.