AI authored to showcase ironpad capabilities.
Pin exists because the compiler started generating self-referential structs on your behalf. Once an async fn desugars to a state machine that holds a borrow across an await point, every Rust programmer owns self-referential values, most of them unwittingly, and the language needs a way to say this one cannot move now.
The bug that forces all of it takes four lines and fails silently. Start there.
Every cell below compiles to WebAssembly and runs on this page, including the one that fails by design, where the compiler's diagnostic is the output.
The struct holds a String and a raw pointer. Directing the pointer at its own field is legal, and for exactly one statement it remains accurate. Then let b = a; performs a memcpy the compiler may execute at its discretion, and the pointer retains the address it was given.
Reading through the stale pointer is undefined behavior, and it returns "hello" regardless, because the vacated slot has not been reused yet. Nothing trapped and nothing warned. A defect that reproduces on demand is tractable; this one waits for an incidental change in stack layout, then corrupts unrelated state in a distant function.
Returning a value is a move, so the constructor invalidates the pointer as it returns. Every obvious remedy fails for the same reason. Heap-allocate and the Box still moves. Re-establish the pointer after every move and you must intercept every move, for which Rust exposes no hook. Forbid moves by convention and the convention is enforced by nobody.
The value must reside at a fixed address for as long as the reference exists, and the type system must be able to enforce it.
ouroboros eliminates the discipline problem by generating the struct for you: declare which field borrows which, and the macro emits a constructor plus closure-scoped accessors. Around 74 million downloads suggest the pattern is common enough to warrant a crate.
Every self-reference to this point was deliberate. You wrote the raw pointer, or you reached for the macro. #[coroutine] is where the compiler manufactures one unprompted.
A coroutine that borrows a local and then suspends must retain both across the suspension point: the owned value, and a reference into it. That is the struct from the first cell, generated on your behalf, and the borrow checker permits it because the compiler controls the layout.
Coroutine::resume takes Pin<&mut Self>, not &mut self. That single choice is the whole mechanism. Advancing a coroutine requires proving it is pinned first, and pinning is the promise that the value will not move again for the remainder of its lifetime.
pin! makes that promise on the stack by shadowing the original binding, so the coroutine is no longer reachable by a name you could move out of.
Calling resume without pinning fails to compile, and the diagnostic is the output.
async fn was a coroutine the whole timeAn async fn compiles to the same kind of state machine. Locals that survive an .await become fields, and a borrow held across one becomes a field pointing at a sibling field. Future::poll therefore takes Pin<&mut Self> for exactly the reason resume does.
That is also why a future you want to store in a Vec, or return as Box<dyn Future>, requires Box::pin: the heap furnishes the stable address the borrow depends on.
The next cell drives a future by hand, with no executor, to expose the pinning requirement.
Most types can be moved freely even after pinning, because they contain no interior references. Those implement Unpin automatically, and Pin<&mut T> where T: Unpin hands back an ordinary &mut T at no cost. Pin is therefore inert for Vec, String, i32, and very nearly everything written by hand.
The types that are not Unpin are almost exclusively compiler-generated: coroutines, and the futures built on top of them. Pin is a narrow instrument aimed at a narrow problem, and the problem only became ubiquitous when async made generated state machines routine.
Three ways to build the same broken struct, then: by hand with a raw pointer, deliberately with a macro, and accidentally by writing .await. Only the third is common, which is why the fix had to live in the type system rather than in a lint.