Theme
Q057. What does an async fn compile into?
Short Answer
Calling an async fn creates an anonymous type that implements Future; it does not execute the function body to completion. Conceptually, the compiler transforms the body into a state machine with one state for each suspension point. Locals needed after .await become fields. Polling runs the machine until it completes or reaches an operation that returns Pending.
Deep Dive
This function:
rust
async fn answer() -> u64 {
another_future().await + 1
}is conceptually similar to a normal function returning impl Future<Output = u64>. The exact generated type is private and compiler-defined.
Code between suspension points runs synchronously during a poll. At .await, the child future is polled. If it is ready, execution continues in the same poll. If it is pending, the parent records its current state and returns Pending.
The generated future's size depends on captured data and the largest state that must be stored, not the sum of every local in every branch. Large futures can increase task memory and movement costs before they are pinned.
Internal Model
The compiler lowers async code through intermediate representations into a generator-like state machine. A discriminant identifies the current state, and fields store live locals and child futures.
Drop glue is state-aware: cancellation drops only fields initialized in the current state. Optimization can remove fields, merge states, and inline polling code.
Example
rust
use std::future::Future;
async fn answer() -> u64 {
tokio::task::yield_now().await;
42
}
fn make_answer() -> impl Future<Output = u64> {
answer()
}
#[tokio::main]
async fn main() {
assert_eq!(make_answer().await, 42);
}Common Mistakes
- Saying
async fnimmediately starts a background operation. - Assuming each
.awaitcreates a new OS thread. - Treating generated future layout as a stable ABI.
Follow-ups
- Which local variables become fields of the generated future?
- Why can one non-
Sendlocal make the whole future non-Send? - How can boxing reduce the size of a parent future?
Related
- Q044. How does an OS thread differ from an async task?
- Q056. What is a
Future? - Q062. What happens internally at an
.awaitpoint?
References
- Async Book
- Rust Reference
- rustc-dev-guide