We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 3 of 25
The Future trait: Output, Poll, and your first hand-written future
A future is a value that represents work which may not have finished yet.
In Rust, a future is any type that implements the Future trait from
std::future. An async fn returns a value of a compiler-generated type
that implements this trait, and .await drives a future by calling the
method below. Leaving out its attributes, the trait has had this shape since
it became stable in Rust 1.36:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
type Output is an associated type: each implementation names the type of
value the future produces when it finishes. poll is the only method.
In this exercise you implement the trait by hand for two types and then poll them yourself.
pub struct Immediate<T>(pub Option<T>); // finishes on its first poll
pub struct NeverDone; // never finishes
pub fn probe(vals: Vec<i32>, poll_never: bool) -> Vec<Option<i32>>
The contract
Immediate<T>hasOutput = T. Its first poll returnsPoll::Ready(value), taking the value out of theOption.NeverDonehasOutput = i32. Every poll returnsPoll::Pending.probegoes throughvalsin order. For each valuevit creates a newImmediate(Some(v)), polls it exactly once, and pushesSome(x)if the poll returnedReady(x)orNoneif it returnedPending.- After that, if
poll_neveris true,probecreates oneNeverDone, polls it once, and pushes the result in the same way. Ifpoll_neveris false, nothing more is pushed.
So the result is every value of vals, in its original order and unchanged,
followed by one None when poll_never is true. vals may be empty, and its
values can be any i32, including i32::MIN and i32::MAX. The grader shows
the result as a JSON array in which None appears as null: for
vals = [1, 2, 3] and poll_never = true the expected output is
[1, 2, 3, null], and for an empty vals with poll_never = true it is
[null].
How to read poll
poll asks the future to make as much progress as it can right now, without
blocking the thread, and to report whether it has finished. It returns this
enum from std::task (note: Poll is in std::task, not std::future):
enum Poll<T> { Ready(T), Pending }
Ready(value) means the future has finished and hands over its output.
Pending means it has not finished yet and the caller should poll again
later.
The two parameters need only a short explanation for now:
self: Pin<&mut Self>is a mutable reference wrapped inPin.Pinguarantees that, once a value is pinned, it stays at the same memory address until it is dropped, unless its type implements theUnpintrait.Unpinmeans the type does not depend on staying at one address, so moving it is harmless.Unpinis an auto trait: the compiler implements it for a struct automatically when every field isUnpin.NeverDonehas no fields, so it isUnpin;Immediate<T>isUnpinwheneverTis. For anUnpintype,Pin<&mut Self>lets you read and modify fields as if it were&mut Self, which is whyself.0.take()works here. A later item in this track explains what pinning is for.cx: &mut Context<'_>carries the waker, the handle a future uses to ask to be polled again once it can make progress. Neither future here uses it:Immediateis finished at once andNeverDonenever will be. Later items in this track build futures that do.
Building a Context to poll with
To call poll you need a Context, and Context::from_waker builds one
from a &Waker. The standard library provides a waker that does nothing when
asked to wake a task, which is all this exercise needs:
let mut cx = Context::from_waker(Waker::noop());
Waker::noop() is stable since Rust 1.85 and already returns a
&'static Waker, so it can be passed directly.
Polling a future by hand
poll takes Pin<&mut Self>, not &mut self, so you first wrap a mutable
reference in Pin. Pin::new does this safely, but only for Unpin types;
for any other type it fails to compile with E0277. It is only a wrapper
around the reference and does not move or copy the future:
let mut fut = Immediate(Some(7));
let p = Pin::new(&mut fut).poll(&mut cx); // Poll::Ready(7)
Calling poll as a method requires the Future trait to be in scope. In the
2024 edition it is in the prelude, so this works even without the starter's
use std::future::Future;; in the 2018 and 2021 editions that import is
required.
The compiler error in the starter
The starter's impl Future for Immediate<T> does not say what Output is,
so it fails to compile with E0046, "not all trait items implemented,
missing: Output". An implementation must define every associated type the
trait declares. Immediate<T> produces a T. NeverDone never produces a
value, but its implementation still has to name an Output type; the
starter already uses i32.
The starter's impl also has the bound T: Unpin. Remove it and the
compiler rejects self.0.take() with E0596, because modifying a field
through Pin<&mut Self> is only allowed when Self is Unpin.
Polling after Ready
Immediate hands out its value with Option::take, which leaves None
behind, so a second poll reaches the expect and panics. That is allowed.
The Future documentation says that once a future has returned Ready,
callers should not poll it again, and that if they do, the future may panic,
never finish, or misbehave in some other way, as long as it does not cause
undefined behavior. probe polls each Immediate only once, so the panic
never happens in this exercise.
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.