We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 4 of 25
`block_on`: the smallest thing that can run a future
Rust’s standard library ships async/.await syntax and the Future trait —
and no executor. Nothing in std will run a future for you. That is a
deliberate decision: a kernel, a microcontroller and a web server want
completely different schedulers, so the language stays out of it.
The consequence is that async Rust does not work until somebody writes a driver. Today that somebody is you.
pub fn block_on<F: Future>(fut: F) -> F::Output
pub fn run_to_completion(steps: u32) -> u32
You are given a Countdown future that returns Pending steps times and
then resolves to the number of times it was polled. Write block_on, and make
run_to_completion(steps) drive a Countdown and return that count. The
answer is always steps + 1: steps pending polls plus the one that finishes.
The whole driver
Four ingredients, and nothing else:
-
Pin the future.
polldemandsPin<&mut Self>, so the future has to sit at a fixed address.std::pin::pin!(fut)(stable 1.68) pins it on the stack and gives you back aPin<&mut F>.Box::pin(fut)does the same on the heap. -
Build a
Context.Context::from_waker(Waker::noop())— rememberWaker::noop()already hands you a&'static Waker, so no extra&. -
Loop. Poll. On
Ready(v), returnv. OnPending, go round again. - That is it.
The stumble that is guaranteed to happen
The starter contains it, so you will meet it immediately:
loop {
match fut.poll(&mut cx) { ... }
}
E0382: use of moved value: fut. pin! gives you a Pin<&mut F>, and
Future::poll takes self: Pin<&mut Self> by value. The first iteration
consumes the pin; there is nothing left for the second. Pin is not Copy,
and it deliberately is not — handing out two Pin<&mut T> to the same place
would be handing out two &mut.
The fix is Pin::as_mut, which reborrows:
fut.as_mut().poll(&mut cx)
as_mut takes &mut self and produces a shorter-lived Pin<&mut F>, leaving
the original intact. This is the pin equivalent of reborrowing a &mut, and
you will use it in every single future you write from here on.
Name the flaw before it bites you
The driver you just wrote busy-spins. It re-polls unconditionally, as fast as the CPU allows, whether or not the future could possibly have made progress. Two consequences:
- It burns a core for nothing. Real drivers park the thread and let the waker unpark it.
-
More interestingly, it hides bugs. A future that returns
Pendingand forgets to register the waker is broken — but this driver polls it again anyway, so it works. Swap in a correct driver and the same future hangs forever with zero CPU usage.
That failure has a name, lost wakeup, and it is the subject of items 16.6
and 16.7. For now, notice that the Countdown you were given calls
cx.waker().wake_by_ref() before returning Pending, even though this driver
does not need it to. That is politeness that will turn out to be a
requirement.
Watch the loop
If you take the poll out of the loop body by accident you get loop {}, which
clippy rejects as clippy::empty_loop — a genuinely useful accident, because
a bare loop {} in a driver is an infinite hang rather than a compile error
in most languages.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.