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: poll a future to completion
Calling an async function creates a future. Something must poll that future
to obtain its result. Rust provides the async and .await language syntax;
the standard library provides types such as Future, Context and Waker.
In this exercise, you will use those types to build a small polling loop.
Your task
Repair block_on in the starter and keep run_to_completion as its caller:
pub fn block_on<F: Future>(fut: F) -> F::Output
pub fn run_to_completion(steps: u32) -> u32
The supplied Countdown returns Pending on its first steps polls, then
returns Ready with the total poll count. For example:
steps |
Poll results | Return value |
|---|---|---|
| 0 | Ready(1) |
1 |
| 1 | Pending, Ready(2) |
2 |
| 2 | Pending, Pending, Ready(3) |
3 |
Inputs are small enough to finish within the time limit and satisfy
steps < u32::MAX, so steps + 1 fits in the return type. Do not change
Countdown or replace the generic driver with an arithmetic shortcut.
Set up the poll
The starter already performs two setup steps:
pin!(fut)pins the future locally and returnsPin<&mut F>. This letsblock_onaccept futures without requiringF: Unpin. The macro does not allocate a new heap object; the future itself may still own allocations.Context::from_waker(Waker::noop())creates the polling context. The no-op waker ignores wake requests.Waker::noop()already returns a reference, so no extra&is needed.
Local pinning is useful for futures whose state must stay at the same
address. Types that implement Unpin do not need that restriction.
See the pin! documentation
for the distinction between local pinning and heap allocation.
Reborrow on each iteration
The starter calls fut.poll(&mut cx) inside the loop. This produces E0382:
the first call moves the Pin<&mut F> wrapper, leaving no wrapper for the
next iteration. Moving this wrapper is different from moving the future
stored behind it.
Use this expression for each poll:
fut.as_mut().poll(&mut cx)
as_mut() creates a shorter-lived pinned mutable reference. The original
wrapper remains available for the next iteration. Pin<&mut F> is not
Copy, because its mutable reference is not Copy; that does not mean
every kind of Pin is non-copyable.
On Ready(value), return value immediately. On Pending, try another poll.
The driver should never poll a completed future again. Its output remains
generic: block_on(std::future::ready(String::from("done"))) returns the
string, even though this exercise's wrapper returns a number.
Why this is only a teaching driver
This loop busy-polls: it keeps calling poll without waiting for a wake
request. That works for Countdown, which makes progress on every call.
A future waiting for network input could instead consume CPU while making
no progress. Runtime-specific futures may also require services this driver
does not provide.
A wake-aware executor schedules another poll when work can progress. It may
run other tasks or wait when none are ready. Countdown calls
wake_by_ref() before each Pending, requesting another poll immediately;
it does not need to save the waker for later.
Busy-polling can hide a missing wake request: another poll happens anyway. Under a wake-aware executor, a future that never arranges its next wake can remain unfinished. Building a working waker and avoiding missed wakeups are the next steps toward a useful executor.
The Future contract
explains polling and wakeups. This exercise deliberately uses a simpler
loop so you can focus on pinning, reborrowing and returning the result.
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.