Theme
Q058. Why are Rust futures lazy?
Short Answer
Rust futures make progress only when polled. Calling an async fn constructs a future but does not independently schedule it. Laziness allows futures to be composed without immediately starting work, avoids requiring one global runtime, and makes execution and cancellation explicit. A forgotten future may therefore do nothing, often producing an unused_must_use warning.
Deep Dive
Laziness separates a computation's description from its execution. Combinators and .await can build one larger future, and an executor decides when and where to poll it.
This differs from eager promise models where creating a promise may immediately schedule work. In Rust:
.awaitdrives a child future as part of the current task;spawnregisters a future as an independently scheduled task;- dropping an uncompleted future cancels its remaining computation.
Some ordinary code may run before a future is returned if it is outside the async block. The guarantee is not that every API returning a future has no side effects; it is that a future itself advances only through polling.
Internal Model
Constructing a generated future initializes its captured fields and initial state. The body runs when poll enters that state.
The #[must_use] annotation on futures helps catch accidental construction without awaiting or polling, but it cannot decide whether intentionally dropping a future is a bug.
Example
rust
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
fn operation(started: Arc<AtomicBool>) -> impl std::future::Future<Output = ()> {
async move {
started.store(true, Ordering::SeqCst);
}
}
#[tokio::main]
async fn main() {
let started = Arc::new(AtomicBool::new(false));
let future = operation(Arc::clone(&started));
assert!(!started.load(Ordering::SeqCst));
future.await;
assert!(started.load(Ordering::SeqCst));
}Common Mistakes
- Assuming calling an
async fnschedules it. - Creating a future, assigning it to
_, and expecting its side effects. - Claiming an API cannot do any eager work before returning a future.
Follow-ups
- What is the difference between awaiting and spawning a future?
- How does dropping a lazy future implement cancellation?
- Why does Rust not include a built-in global executor?
Related
- Q056. What is a
Future? - Q061. What is the relationship between a future, a task, and an executor?
- Q065. What is the difference between awaiting a future and spawning a task?
References
- Rust Standard Library
- Async Book