We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 2 of 25
Futures are lazy: nothing happens until you poll
Calling an async fn does not run it. This problem makes you prove that
to yourself.
pub fn laziness_log(tags: Vec<String>, drive: Vec<bool>) -> Vec<String>
You are given an async fn record(tag, log) whose entire body is one line:
it appends ran:<tag> to a shared log. You are also given a block_on that
drives a future to completion. Produce the log for this exact script:
-
Pass 1 — for each tag in order, append
constructed:<tag>to the log and then constructrecord(tag, log). Keep the future; do not drive it. -
Pass 2 — walk the tags in order again. For each one whose
driveflag isfalse, appenddropped:<tag>and then drop that future. -
Pass 3 — walk the tags in order a third time and
block_onevery future whose flag istrue. - Return the log.
So tags = ["a", "b"] with drive = [true, false] must produce
["constructed:a", "constructed:b", "dropped:b", "ran:a"]
Read that output again. record("b", ..) was called. Its future existed
for the whole of pass 1. And ran:b is not in the log, because the body never
ran — the future was dropped before anybody polled it.
Why this is the first thing in the track
If you are coming from JavaScript, C#, Python or Go, your instinct is that
calling an async function starts it. In JS, fetch(url) sends the request
before you ever await the promise; dropping the promise on the floor does
not un-send it. Rust is the opposite, and it is opposite on purpose:
-
async fn f()compiles to an ordinary function that constructs and returns a value. That value is an inert state machine parked at “before the first line”. -
The body runs only inside
poll, andpollis only ever called by whoever is driving the future — here,block_on. -
Therefore dropping a future is a complete, instantaneous cancellation. There
is no thread to signal, no task to unregister, no
CancellationToken. The work had not started.
Everything later in this track depends on that one fact: cancellation by
drop, the zero-cost state machine, select! discarding the loser. They are
all consequences of laziness.
Two things that will bite you
.await does not work here. laziness_log is a plain fn, and the
compiler will tell you so with E0728: “await is only allowed inside
async functions and blocks”. That is not a bug in your code; it is the
colouring rule. Inside a synchronous function the only way to run a future is
to hand it to a driver, which is exactly what block_on is for.
let _ = fut; and let _fut = fut; are not the same. The first drops the
future immediately — _ is not a binding, it is a discard. The second binds
it to a variable named _fut that lives to the end of the scope. That
difference has bitten enough people that clippy ships a lint for it,
clippy::let_underscore_future; it is in the restriction group, so it is
off by default and will not fire on the gate here. Knowing it exists is the
point.
Storing futures you have not driven yet
Every call to record returns the same opaque type, so a Vec of them
type-checks even though you cannot name that type. You need to drop some and
drive others, which means each slot has to be emptiable — a Vec<Option<_>>
with take() is the natural shape. Assigning None into a slot drops
whatever was there, right at that moment.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.