Waiting for Delay with .await
Goal of This Episode
Wait on last episode’s Delay with .await, and watch with your own eyes — via println! — how a Future “pauses and resumes.”
Main Text
Printing Around the .awaits
We have Delay, and we have block_on. Now put Delay inside an async block, wait on it with .await, and add println! before and after every .await to observe the order of execution:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, Waker};
use std::time::{Duration, Instant};
struct Delay {
when: Instant,
}
impl Delay {
fn new(duration: Duration) -> Delay {
Delay {
when: Instant::now() + duration
}
}
}
impl Future for Delay {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
if Instant::now() >= self.when {
Poll::Ready(())
} else {
Poll::Pending
}
}
}
fn block_on<F: Future>(future: F) -> F::Output {
let mut future = Box::pin(future);
let mut cx = Context::from_waker(Waker::noop());
loop {
match future.as_mut().poll(&mut cx) {
Poll::Ready(value) => return value,
Poll::Pending => {}
}
}
}
fn main() {
block_on(async {
println!("start");
println!("waiting for the first delay...");
Delay::new(Duration::from_secs(1)).await;
println!("first delay done, moving on");
println!("waiting for the second delay...");
Delay::new(Duration::from_secs(1)).await;
println!("second delay done, moving on");
});
}
Run it, and the output appears step by step, like this:
start
waiting for the first delay...
(one-second pause)
first delay done, moving on
waiting for the second delay...
(one-second pause)
second delay done, moving on
How It “Pauses and Resumes”
This output order reveals how a Future operates. Remember, the whole async block is itself a Future, and block_on keeps polling it:
- First
poll: it runs from the top, prints “start” and “waiting for the first delay…”, then hits the first.await. TheDelayhasn’t expired, so it returnsPending— and the wholeasyncblock returnsPendingalong with it, pausing right here. - The executor
polls again and again, but theDelaystill isn’t due; each time it gets stuck at that first.awaitreturningPending, unable to move on. - A second later, the
Delayduly returnsReady(()). Thispollgets past the first.await, prints “first delay done” and “waiting for the second delay…”, hits the second.await, and returnsPendingagain — paused at a new spot. - One more second, the second
Delayduly returnsReady(()); it clears the second.await, prints the final line, the wholeasyncblock returnsReady, andblock_onfinishes.
The crux: each time it’s polled, the Future picks up from where it last paused, running until the next not-yet-ready .await where it may stop. This ability to “remember progress, pause, and resume from the same spot” is delivered by the “state machine” mentioned earlier — but this episode, just watch the phenomenon.
.await Doesn’t Give You Concurrency for Free
Note something important: the two Delays above were waited on one after the other, taking two seconds in total. The second Delay started its countdown only after the first finished.
This trips up beginners a lot. .await means “wait for this to be ready” — it does not automatically make your program concurrent. Two .awaits in a row wait dutifully in sequence; there’s no cleverness that “waits on both together.”
So what if I do want both Delays timing simultaneously, one second total? That’s next episode’s topic — we’ll build, by hand, a tool that advances multiple Futures concurrently.
Recap
- Waiting on
Delaywith.awaitinsideasync, plusprintln!, lets you watch execution step forward. - Each
pollresumes theFuturefrom where it last paused, until the next unfinished.awaitreturnsPending. - A
Futureremembers its progress and resumes in place — the state machine behind it deserves the credit. .awaitdoes not give you concurrency for free: two consecutive.awaits wait in sequence; concurrency needs other tools (one comes next episode).