164. Pin projection — How to actually use the fields behind Pin<&mut Self>
The moment you hand-roll Future::poll, you have a Pin<&mut Self> and a question Rust won’t answer for you: how do I touch my fields? self.inner doesn’t compile, &mut self.inner is what Pin exists to prevent, and the answer — pin projection — is one of those idioms everyone reinvents until they reach for pin-project-lite.
bite-162 covered what Pin<P> is and why async futures need it. This one is about the very next thing you trip over: actually polling the inner future from your own poll method.
The problem
A wrapper that polls an inner future and counts how many times it was polled:
| |
Pin<&mut Self> deliberately won’t deref-mut into &mut Self — that would hand back the exact &mut you need to mem::swap the whole struct out from under whatever pinned it. So self.inner is a non-starter. You have to project: turn a Pin<&mut Self> into a Pin<&mut F> pointing at the inner field.
Manual projection with unsafe
The raw tools are Pin::get_unchecked_mut and Pin::new_unchecked. You take &mut Self out of the pin (unsafe — you’re promising not to move the whole value), borrow disjoint fields, then re-pin the ones that need it.
| |
Two unsafe blocks and an invariant you have to remember everywhere else in the file: if some other method ever does mem::replace(&mut this.inner, _), you’ve broken the pin contract and quietly created UB. The compiler will not catch it.
The clean answer: pin-project-lite
pin-project-lite mechanically derives the safe projection. Mark each structurally-pinned field with #[pin]:
| |
self.project() returns a generated struct where every #[pin] field is a Pin<&mut Field> and every other field is a plain &mut Field. No unsafe, no projection mistakes, no chance of accidentally mem::replace-ing a pinned field — the macro generates the accessors so the wrong move never compiles. This is the pattern tokio, hyper, futures, and effectively every library implementing custom futures lives on.
Structural vs non-structural — the choice you’re making
Marking a field #[pin] locks in three guarantees:
- You will never move out of it once
Selfis pinned (nomem::replace, nomem::swap). - Its
Dropimpl runs while the field is still pinned. - Accessors hand you
Pin<&mut Field>, not&mut Field.
Unmarked fields go the other way: you treat them as freely movable. Pick wrong — pin one structurally and then mem::swap it elsewhere — and you’ve quietly invalidated whatever pointers something else handed out into that field.
Rule of thumb: if a field is itself a future, or any !Unpin type that needs to be polled in place, mark it #[pin]. Counters, flags, owned Strings — leave them unmarked.