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
A future's poll method receives self: Pin<&mut Self>, not &mut self.
A pinned value is one that promises not to move to a different memory
address until it is dropped. A combinator (a future that wraps and drives
other futures) usually needs to update its own fields inside poll, so it
has to get mutable access through that Pin. This exercise shows the
simplest safe way to do that: store the child future in a Pin<Box<_>>.
The starter code does not compile. Your job is to make it compile and work
without writing unsafe.
pub struct Wrapper<F: Future> { inner: F, count: u32 }
pub fn count_polls(pends: u32) -> u32
The contract
Wrapper counts how many times its own poll has been called. Each call to
Wrapper::poll adds 1 to the count and then polls the child once. If the
child returns Poll::Ready, the wrapper returns Poll::Ready(count);
otherwise it returns Poll::Pending.
count_polls(pends) wraps the provided future wait(pends) and runs it with
the provided block_on. The provided Countdown future returns
Poll::Pending exactly pends times and then Poll::Ready(()), so the
wrapper is polled pends + 1 times and count_polls(pends) returns
pends + 1. For example, count_polls(0) returns 1 and
count_polls(3) returns 4.
Tests use 0 <= pends <= 1000. The provided block_on stops with a panic
after 100,000 polls, so it can only complete inputs up to 99_999; you do
not need to change that limit. Keep Countdown, wait, block_on and the
signatures of Wrapper::new and count_polls as they are. You may change
the type of Wrapper's private inner field.
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>>`
Pin<Ptr> is a standard library type that wraps a pointer such as &mut T
or Box<T>. Reading through it is always allowed when Ptr implements
Deref, so self.count can be read even in the starter. Writing through it
with *self needs DerefMut, and the standard library only provides that
when the pointed-to type implements Unpin. In simplified form, the
standard library's implementation is:
impl<Ptr> DerefMut for Pin<Ptr>
where
Ptr: DerefMut,
Ptr::Target: Unpin,
{ .. }
The reason is that a plain &mut T lets safe code move the value, for
example with std::mem::swap or std::mem::replace. For a type that relies
on staying at one address, that would break the pinning promise. Unpin
is the marker for types that do not rely on their address, so for those
types handing out &mut T is harmless. The other safe ways to get &mut T
out of a pin follow the same rule: Pin::get_mut also requires T: Unpin, and the only
ways around it are unsafe functions such as Pin::get_unchecked_mut.
Unpin is an auto trait. The compiler implements it automatically for a
struct when every field implements it, unless the struct has an explicit
implementation. Most ordinary types, including u32, are Unpin. Futures
created by async fn and async blocks are not, even when they hold no
local variables.
The starter's Wrapper<F> stores F directly. Inside the generic impl,
the compiler only knows F: Future, so it cannot prove Wrapper<F>: Unpin,
and &mut *self is rejected. In count_polls, F is the async fn future
returned by wait, which really is not Unpin.
Three ways to get &mut Self without unsafe
- Make every field
Unpin. Then the struct isUnpinautomatically and&mut *selfcompiles. - Call
Pin::get_mut(self). It is safe and returns&mut Self, but it has the sameSelf: Unpinrequirement as&mut *self. - Put the child future behind
Box::pin.Box<T>isUnpinfor everyT, soPin<Box<F>>isUnpinfor everyF. The future stays at a fixed heap address; moving thePin<Box<F>>only moves the box pointer.
Adding an F: Unpin bound to the impl is not a fix here. The library
code would compile, but count_polls would then fail with E0277 because
the future returned by wait is not Unpin.
Option 3 is the fix for this exercise, and it is really option 1 applied to
the inner field: once inner has type Pin<Box<F>>, every field of
Wrapper<F> is Unpin, so &mut *self compiles. You then poll the child
with this.inner.as_mut().poll(cx). Pin::as_mut turns a
&mut Pin<Box<F>> into a short-lived Pin<&mut F>, which is what
Future::poll needs, without moving the box.
Implementing Unpin by hand
impl<F: Future> Unpin for Wrapper<F> {} is a safe implementation and needs
no unsafe keyword. (The F: Future bound has to be repeated because the
struct declares it; without it the impl fails with E0277.) Safe code alone cannot misuse it: with a plain F field,
it gives you &mut F, but polling that field would still need either
F: Unpin or an unsafe call such as Pin::new_unchecked.
The obligation falls on any unsafe code that creates a Pin<&mut Field>
from a Pin<&mut Self>, which is called pin projection. A field accessed
that way is called structurally pinned, and that code is only sound if the
containing type implements Unpin only when that field's type does too. A hand-written
Unpin implementation can therefore make existing unsafe projection code
unsound. In the boxed version, nothing needs to be projected, so no such
conflict exists.
Cost
Box::pin performs one heap allocation for each boxed child future that is
not zero-sized, and polling the child goes through one extra pointer. Library combinators,
such as those in the futures crate, often store children inline and use
pin projection (usually generated by a crate such as pin-project-lite) to
avoid that allocation. Whether the allocation matters for your code is
something to measure rather than assume.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
Rust reference solution
Sign in to track your attempts toward unlocking the reference solution.