We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 11 of 25
`Unpin` in practice: making `Pin<&mut Self>` usable without unsafe
Every combinator in this track needs to reach its own fields from inside
poll, and poll hands you a Pin<&mut Self> rather than a &mut self.
This problem is about the cheapest way to make that work — cheap enough that
the rest of the track needs no unsafe at all.
The starter does not compile. That is the problem.
pub struct Wrapper<F: Future> { inner: F, count: u32 }
pub fn count_polls(pends: u32) -> u32
Wrapper counts how many times it was polled and resolves to that count when
its child does. count_polls(pends) wraps a given async fn that pends
pends times, drives it, and returns the count — always pends + 1.
The error
error[E0596]: cannot borrow data in dereference of `Pin<&mut Wrapper<F>>` as mutable
= help: trait `DerefMut` is required to modify through a dereference,
but it is not implemented for `Pin<&mut Wrapper<F>>`
Here is the whole mechanism, and it is smaller than it looks. Pin<Ptr> is an
ordinary library type. It implements Deref always, and DerefMut only
when the pointee is Unpin:
impl<Ptr: DerefMut> DerefMut for Pin<Ptr> where Ptr::Target: Unpin { .. }
That single where clause is the entire enforcement mechanism. Handing out
&mut T would let anyone call mem::replace or mem::swap and move the
value out of its pinned address, so the API simply refuses to produce one
unless T has said, via Unpin, that it does not care where it lives.
Unpin is an auto trait: nearly every type gets it automatically, and a
struct gets it exactly when all of its fields have it. Wrapper<F> stores an
F inline and F is only known to be Future — it could be an async fn
future, which is !Unpin. So Wrapper<F> is not Unpin either, and the
deref is refused.
Three ways out
-
Let auto-
Unpindo it. If every field is alreadyUnpin, the struct is too, and&mut *selfcompiles with no ceremony. -
Pin::get_mut(self). A safe function, available whenSelf: Unpin. Same requirement, different spelling. -
Box the address-sensitive child.
Pin<Box<F>>isUnpinfor anyF, because theBoxis what holds the address steady — moving thePin<Box<F>>moves a pointer, not the future. Store the child that way and the parent becomes genuinely movable.
Option 3 is the fix here, and it is the rule the whole combinator tier of this
track runs on: if the children live behind Box::pin, the parent is Unpin
and needs no pin projection at all. You reach the child with
self.inner.as_mut().poll(cx) — Pin::as_mut reborrows, exactly as it did in
your block_on.
Unpin is a safe trait — and that is not a licence
impl Unpin for MyType {} needs no unsafe. Say that out loud, because
everything else near Pin looks like it should. You are allowed to write it.
But “safe to write” is not “always sound”. A hand-written Unpin impl is only
correct when no field is structurally pinned — when nothing inside the
struct is relying on its address. Boxing the child is what makes that true
here. If you later add a bare F field to the same struct and leave the impl
in place, you have created real unsoundness with no unsafe keyword anywhere
in sight. Item 16.16 shows the version where the field genuinely is pinned in
place, and what you owe the compiler then.
Cost
One heap allocation per child. That is why futures and tokio do the harder
thing and store children inline with pin projection. It is also why almost
nobody needs to: Box::pin is fine unless you are writing a library that
allocates on a hot path.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.