Pinning
To poll futures, they must be pinned using a special type called
Pin<T>
. If you read the explanation of the Future
trait in the
previous section "Executing Future
s and Tasks", you'll recognise
Pin
from the self: Pin<&mut Self>
in the Future:poll
method's definition.
But what does it mean, and why do we need it?
Why Pinning
Pinning makes it possible to guarantee that an object won't ever be moved.
To understand why this is necessary, we need to remember how async
/.await
works. Consider the following code:
# #![allow(unused_variables)] #fn main() { let fut_one = ...; let fut_two = ...; async move { fut_one.await; fut_two.await; } #}
Under the hood, this creates an anonymous type that implements Future
,
providing a poll
method that looks something like this:
# #![allow(unused_variables)] #fn main() { // The `Future` type generated by our `async { ... }` block struct AsyncFuture { fut_one: FutOne, fut_two: FutTwo, state: State, } // List of states our `async` block can be in enum State { AwaitingFutOne, AwaitingFutTwo, Done, } impl Future for AsyncFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { loop { match self.state { State::AwaitingFutOne => match self.fut_one.poll(..) { Poll::Ready(()) => self.state = State::AwaitingFutTwo, Poll::Pending => return Poll::Pending, } State::AwaitingFutTwo => match self.fut_two.poll(..) { Poll::Ready(()) => self.state = State::Done, Poll::Pending => return Poll::Pending, } State::Done => return Poll::Ready(()), } } } } #}
When poll
is first called, it will poll fut_one
. If fut_one
can't
complete, AsyncFuture::poll
will return. Future calls to poll
will pick
up where the previous one left off. This process continues until the future
is able to successfully complete.
However, what happens if we have an async
block that uses references?
For example:
# #![allow(unused_variables)] #fn main() { async { let mut x = [0; 128]; let read_into_buf_fut = read_into_buf(&mut x); read_into_buf_fut.await; println!("{:?}", x); } #}
What struct does this compile down to?
# #![allow(unused_variables)] #fn main() { struct ReadIntoBuf<'a> { buf: &'a mut [u8], // points to `x` below } struct AsyncFuture { x: [u8; 128], read_into_buf_fut: ReadIntoBuf<'what_lifetime?>, } #}
Here, the ReadIntoBuf
future holds a reference into the other field of our
structure, x
. However, if AsyncFuture
is moved, the location of x
will
move as well, invalidating the pointer stored in read_into_buf_fut.buf
.
Pinning futures to a particular spot in memory prevents this problem, making
it safe to create references to values inside an async
block.
How to Use Pinning
The Pin
type wraps pointer types, guaranteeing that the values behind the
pointer won't be moved. For example, Pin<&mut T>
, Pin<&T>
,
Pin<Box<T>>
all guarantee that T
won't be moved.
Most types don't have a problem being moved. These types implement a trait
called Unpin
. Pointers to Unpin
types can be freely placed into or taken
out of Pin
. For example, u8
is Unpin
, so Pin<&mut T>
behaves just like
a normal &mut T
.
Some functions require the futures they work with to be Unpin
. To use a
Future
or Stream
that isn't Unpin
with a function that requires
Unpin
types, you'll first have to pin the value using either
Box::pin
(to create a Pin<Box<T>>
) or the pin_utils::pin_mut!
macro
(to create a Pin<&mut T>
). Pin<Box<Fut>>
and Pin<&mut Fut>
can both be
used as futures, and both implement Unpin
.
For example:
# #![allow(unused_variables)] #fn main() { use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io // A function which takes a `Future` that implements `Unpin`. fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { ... } let fut = async { ... }; execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait // Pinning with `Box`: let fut = async { ... }; let fut = Box::pin(fut); execute_unpin_future(fut); // OK // Pinning with `pin_mut!`: let fut = async { ... }; pin_mut!(fut); execute_unpin_future(fut); // OK #}