On this page
Internal Mechanisms of async/await
An
async fnis desugared by the compiler into a state machine implementing theFuturetrait—each.awaitpoint is a state.Pinensures theFuturedoes not move in memory (self-referential structures), andWakerinforms the executor when it can resume polling. Understanding this desugaring process is key to understanding Rust async’s "zero-cost abstraction" and whyFutures cannot be dropped arbitrarily.
What an async fn becomes after compilation
async
The compiler converts the async fn into an anonymous type implementing the Future trait—a state machine:
// Pseudo-code generated by the compiler:
Key point: Each .await point corresponds to a state machine transition. The state machine stores local variables that span across the await—they must remain alive after poll() returns Pending, until the next poll().
The Future trait
The executor repeatedly calls poll(). If the Future is not yet complete, it returns Poll::Pending. Before returning Pending, the Future must register a waker via cx.waker()—a callback function that is invoked when the Future might become ready (e.g., I/O completion, timer expiration). Upon receiving a wake signal, the executor re-polls the Future.
The role of Pin
State1 holds file_content: String. If the Future is moved to a new memory address, internal pointers holding that address (if any) would become dangling. For self-referential Futures (common in select! or scenarios involving borrowed locals), Pin<&mut Self> guarantees that the Future will not be moved—the compiler does not generate memcpy operations to move pinned values.
Skeleton of a simple executor
This is merely conceptual. Real-world executors (tokio, async-std) use epoll/kqueue/IOCP for I/O multiplexing, rather than busy-waiting.
References
- Rust Book: Async chapter (currently in progress for official book)
- Tokio tutorial: tokio.rs/tokio/tutorial
- async-book: rust-lang.github.io/async-book
Keywords: async, await, Future, Pin, poll, state machine, executor, waker