Skip to content

← Async From First Principles step 3 of 25

Easy Primitives

The `Future` trait: `Output`, `Poll`, and your first hand-written future

async fn, .await, tokio, streams — every one of them compiles down to one trait. Here it is, verbatim, as it has been in std since Rust 1.36:

pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

Write two implementations of it by hand, then look at what they do.

pub struct Immediate<T>(pub Option<T>);   // already finished
pub struct NeverDone;                     // never finishes

pub fn probe(vals: Vec<i32>, poll_never: bool) -> Vec<Option<i32>>

probe polls one Immediate(Some(v)) once for every value in vals, recording Some(v) when the poll returns Ready and None when it returns Pending. If poll_never is true it then polls one NeverDone once and records that too. Immediate is always Ready; NeverDone is always Pending; so the output is vals with an optional null on the end.

How to read poll

Read the signature as a sentence: “try to make progress without blocking, and tell me whether you finished.” It returns

enum Poll<T> { Ready(T), Pending }

Poll lives in std::task, not std::future — importing it from the wrong module is the single most common first-minute mistake here.

Two parameters deserve a note and then deliberate silence:

  • self: Pin<&mut Self> — a wrapper that promises the future will not be moved in memory once polling starts. Both of the types here hold nothing address-sensitive, so they are Unpin and self.0 just works. Item 16.10 explains what Pin is actually for, once you have written enough futures to have the question.
  • cx: &mut Context<'_> — the “how to wake me up” handle. Ignore it in this problem; you never return Pending for a real reason yet. Items 16.6 and 16.7 are entirely about it.

Building a Context out of nothing

To poll a future you need a Context, and to build a Context you need a Waker. std ships a do-nothing one:

let mut cx = Context::from_waker(Waker::noop());

Waker::noop() (stable since 1.85) returns a &'static Waker, so there is no & in front of it. Adding one is the classic error.

Polling a future by hand

poll takes Pin<&mut Self>, not &mut self. For a type that is Unpin, Pin::new(&mut fut) builds one safely and for free:

let mut fut = Immediate(Some(7));
let p = Pin::new(&mut fut).poll(&mut cx);

The error you are meant to meet

The starter’s impl Future for Immediate<T> is missing one line. The compiler answers with E0046: “not all trait items implemented, missing: Output“. A trait with an associated type is not satisfied until you say what that type is — and Output is the whole reason the trait exists. Immediate<T> produces a T; NeverDone never produces anything, but it still has to declare a type it would have produced.

You will also see E0277 here if you get a type wrong, because .poll() is only callable on something that implements Future.

One deliberate sharp edge

Immediate hands its value out with Option::take, so polling it a second time panics. That is not a bug: the documented contract says a future must not be polled after it has returned Ready, and panicking is a perfectly respectable way to enforce a contract. Real combinators guard this with an if slot.is_none() check instead — you will write exactly that in item 16.14.