We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 12 of 25
Hand-compile an async fn into a state machine
Repair a hand-written future that performs the same two steps as the supplied
async function. Keep PendOnce, step, pipeline, the boxed child type and
compare unchanged. Fix how Pipeline::poll takes and restores its state.
pub async fn pipeline(n: u32) -> u32 {
let a = step(n).await;
let b = step(a + 1).await;
a + b
}
Each step(n) returns Pending once, requests another poll through the
waker, then returns 2 * n. Your Pipeline must drive those same children
in order and preserve any values needed after they finish.
pub fn compare(inputs: Vec<u32>) -> Vec<(u32, u32)>
For each input, compare returns (async_result, manual_result) in input
order. Both results must equal 6 * n + 2. For example, [0, 3] produces
[(2, 2), (20, 20)]. Empty input returns an empty vector.
Inputs are between 0 and 715_827_882, inclusive, so the intermediate
arithmetic and final result fit in u32. Use the child futures rather than
replacing the state machine with the result formula.
What each state remembers
| State | Work remaining |
|---|---|
Start |
Create the first child from the stored input n. |
AwaitingA |
Poll the first child. |
AwaitingB |
Poll the second child and keep a for the final addition. |
Done |
The result has been returned. |
A future must retain what it needs when a poll returns Pending. Here that
includes the child future and, in AwaitingB, the value a. Locals that are
not retained would otherwise be dropped when the call returns.
This enum is a useful model of an async state machine, not a promise about the compiler's exact representation. Future size depends on retained values, state bookkeeping, layout and optimization. It is not necessarily equal to the size of the largest group of source-level locals.
Take ownership while leaving a valid state
The starter matches directly on this.state and tries to move a boxed child
out of it. this is a mutable reference, so this produces E0507: the move
would leave a field uninitialized behind that reference.
Use a placeholder to take the old state by value:
match std::mem::replace(&mut this.state, State::Done) {
// Handle the owned previous state here.
}
This is a pattern to complete inside poll, not a complete match on its
own. mem::replace installs Done and returns the old state without dropping
it. You can then move its fields into the next state. This is an ownership
operation, not an atomic synchronization operation.
On a child's Pending result, restore its state, including the same child
and any saved a, before returning Pending. On a child's Ready result,
transition forward. Once the second child completes, leave Done in place
and return the sum. Keep the supplied panic for a later poll of Done.
Borrowing the state instead of taking it can work in other designs.
mem::replace fits this starter because its arms already move owned children
between enum variants.
Keep progressing until a child needs to wait
Keep the loop around the match. After Start, poll the first child in the
same call. When it finishes, create and poll the second child in that call.
Return Pending only when the active child does, forwarding the context so
it can request another poll.
Returning Pending just because you changed states can stall a real executor
unless another wake is arranged. An earlier wake may already have been
consumed; it is not a guarantee of a future poll. Polling counts are not the
public contract, but making progress and arranging wakeups are essential.
The supplied driver repeatedly polls with a no-op waker and a 100,000-poll limit. It can complete these children, but can hide a missing-wakeup bug. It is a teaching helper, not a general executor for I/O or timers. The JSON harness checks returned pairs, not wakeup correctness or internal ordering.
Why the children are boxed and pinned
Pin<Box<dyn Future<Output = u32>>> keeps each child at a stable address while
allowing its owning pointer to move between variants. With the supplied
fields, Pipeline is Unpin, so &mut *self is valid. Keeping a potentially
non-Unpin child inline would need a different pin-projection design.
Do not poll a child again after it completes. The general Future contract
does not require repeated polling after Ready to work; this exercise's
manual future deliberately panics in its completed state.
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.