We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 14 of 25
Join two futures and keep both results
Make the supplied generic Join future compile without restricting its
children or their output types to Unpin. Keep the fields, constructor,
polling logic, Countdown and race_log unchanged. Add the missing trait
implementation and explain why the stored children remain pinned.
pub struct Join<A: Future, B: Future> { /* supplied fields */ }
pub fn race_log(a_pends: u32, b_pends: u32) -> (u32, u32, Vec<char>)
Each supplied Countdown records its tag on every poll. It returns Pending
the requested number of times, then Ready on its next poll. The a child
returns 10; the b child returns 20. Join waits for both results.
Despite its name, race_log records a join, not a race that stops at the
first completed child.
Poll unfinished children in a, then b order on each poll of Join.
Once a child finishes, retain its result and skip that child on later polls.
For example:
race_log(3, 1) = (10, 20, ['a', 'b', 'a', 'b', 'a', 'a'])
race_log(0, 0) = (10, 20, ['a', 'b'])
In the first example, b finishes at the fourth log entry. Only a is
polled afterward. The log contains a_pends + 1 copies of 'a' and
b_pends + 1 copies of 'b', interleaved in the required order.
Both pending counts are below 100_000, so the supplied driver's polling
budget is sufficient. That driver repeatedly polls with a no-op waker; it
is a teaching helper for these children, not a general I/O executor.
Preserve a result while the other child waits
ra and rb hold completed outputs. On each poll:
- If
rais empty, pollaand save a ready result. - If
rbis empty, pollband save a ready result. - Return both results when both slots are full; otherwise return
Pending.
The supplied code takes both slots before matching on them. If only one is
full, it restores the slots before returning Pending. Forgetting that
restoration would lose a completed result.
Do not poll a completed child again. The general Future contract does not
require that to work, even though this particular Countdown returns the
same value if polled again. The log also detects those extra polls.
Why the generic implementation fails
The starter needs a mutable reference from Pin<&mut Join<A, B>> at
let this = &mut *self. That requires Join<A, B>: Unpin.
The boxed child fields can move without moving their allocations. However,
the output slots contain arbitrary A::Output and B::Output values, which
need not implement Unpin. Rust therefore cannot derive the required
property automatically for every pair of futures.
Add this safe implementation:
impl<A: Future, B: Future> Unpin for Join<A, B> {}
This is valid for the supplied design because Join does not promise to pin
its output slots. It owns those outputs as ordinary movable values, even
when their types are not Unpin. Only the child futures need stable
addresses, and Pin<Box<_>> preserves those addresses when Join moves.
A future version that stores children inline or exposes pinned references
to fields needs its pinning guarantees reviewed. In particular, a design
cannot promise a field stays pinned and also allow that field to move through
an unconditional Unpin implementation. The trait implementation alone is
safe syntax; the important question is which pinning guarantees the API makes.
Concurrent progress on one thread
This Join polls its children sequentially within each call, on one thread.
Both can make progress before either finishes. It does not create worker
threads or make blocking code inside a child's poll nonblocking.
Awaiting one ordinary lazy future and then another polls the first to completion before polling the second. Joining them can overlap waiting, but does not guarantee a particular latency improvement. Futures representing already-spawned tasks can have work progressing independently of these polls.
Both children receive the parent's context. This simple implementation polls every unfinished child whenever the parent is polled; it does not track which child requested a wake. More elaborate combinators can use separate child wake notifications to avoid scanning a large collection.
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.