We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Async From First Principles step 13 of 25
`poll_fn`: a future from a closure
After several hand-written impl Future blocks you have earned the shortcut.
std::future::poll_fn (stable 1.64) turns a closure of the right shape
straight into a future:
pub fn poll_fn<T, F>(f: F) -> PollFn<F>
where F: FnMut(&mut Context<'_>) -> Poll<T>
That signature is worth staring at. A future is nothing but a thing with a
poll method, so anything with the same shape as poll can be one.
pub fn yield_n(n: u32, log: Rc<RefCell<Vec<u32>>>) -> impl Future<Output = u32>
pub fn drive_all(script: Vec<u32>) -> (Vec<u32>, Vec<u32>)
yield_n(n, log) must, on every poll, append the 1-based number of that
poll to log; return Pending for the first n polls, asking to be polled
again each time; and resolve to the total number of polls it received.
drive_all shares one log across the whole script, drives one yield_n(n, log) per entry in order with the supplied block_on, and returns
(log, results). For script = [2, 0, 1] that is
log = [1, 2, 3, 1, 1, 2] and results = [3, 1, 2].
Where the state lives
The closure you hand to poll_fn is FnMut, so it can own and mutate
captured variables — a counter, a buffer, a handle. Those captures are stored
inside the returned future, and they are pinned along with it. That means
poll_fn state can be self-referential in principle; do not go looking for
that today, item 16.10 is where it belongs.
The practical point is simpler. This is the standard way to bridge a
poll-style API into async: you have something with a try_recv-shaped
function and you need a future, so you wrap it.
The lint you are here to read
The starter returns impl Future<Output = u32> with an async block inside:
pub fn yield_n(..) -> impl Future<Output = u32> {
async move { .. }
}
clippy calls that clippy::manual_async_fn — “this function can be simplified
using the async fn syntax” — and since the gate is -D warnings, it is
fatal. The lint is right about the shape it sees: a plain function whose
whole body is one async block really is just an async fn written the long
way.
But notice what it does not fire on. Returning impl Future is perfectly
fine when the body is a poll_fn call, a std::future::ready, a combinator,
or anything else that is not a bare async block. The lint targets the
redundant wrapper, not the return type. Knowing that distinction means the
message is legible instead of alarming the next time you meet it.
Why an async fn cannot cheat its way out of this
Notice that the log records polls, not iterations. An async block cannot
observe its own polls; only a type that implements Future can. So the
shortest honest route to this specification is poll_fn with a captured
counter — which is exactly the point.
Two neighbours worth knowing
std::future::ready(v) is a future that is Ready on its first poll — the
Immediate you wrote in item 16.3, already in std.
std::future::pending::<T>() never completes. Handing it to a block_on that
busy-spins hangs your program forever; it is only useful inside a race, or
under a driver with a poll budget like the one supplied here.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.