Theme
Q062. What happens internally at an .await point?
Short Answer
.await polls the child future. If it returns Ready, execution continues immediately with its output. If it returns Pending, the parent future records which state to resume from, preserves locals needed later, and returns Pending to its caller. The current task may then run again after a wake-up. .await is therefore a possible suspension point, not an unconditional thread block or task switch.
Deep Dive
Only values live across the suspension need to remain in the generated state machine. Values that are no longer needed can be dropped before the parent returns Pending.
This has practical consequences:
- a lock guard live across
.awaitremains held; - a non-
Sendlocal live across.awaitcan make the future non-Send; - borrows crossing
.awaitbecome part of the future's lifetime and pinning constraints; - cancellation at that point drops the currently initialized state.
Code before the child becomes pending runs synchronously in the current poll. A chain of immediately ready awaits can therefore execute without yielding to other tasks.
Internal Model
Conceptually, lowering changes linear code into states such as Start, WaitingOnChild, and Complete. The child future becomes a field, and the discriminant selects the resume branch.
The executor sees only that the parent returned Pending; it does not inspect source-level await points.
Example
rust
async fn fetch_price() -> u64 {
tokio::task::yield_now().await;
60_000
}
#[tokio::main]
async fn main() {
let symbol = String::from("BTC-USD");
let price = fetch_price().await;
// `symbol` is needed after `.await`, so it survives the suspension.
assert_eq!((symbol.as_str(), price), ("BTC-USD", 60_000));
}Common Mistakes
- Saying
.awaitblocks the current OS thread. - Assuming every
.awaityields to another task. - Forgetting that locals and guards may remain alive across suspension.
Follow-ups
- How does liveness analysis affect the generated future's fields?
- What gets dropped when a future is cancelled while suspended?
- Why can an immediately ready future reduce scheduling fairness?
Related
- Q048. Why is holding a lock guard across
.awaitdangerous? - Q057. What does an
async fncompile into? - Q064. What makes a future
Send, and why doestokio::spawnrequireSend + 'static?
References
- Async Book
- rustc-dev-guide
- Rust Reference