Rust Generator yield: What the Compiler Builds Under async/await
Every async function in Rust is a compiler-generated state machine built on generators. Yield is not a niche feature — it is the foundation of async/await. Most developers use it without realizing it can inflate memory usage and binary size significantly.
The compiler transforms each await into a suspension point and builds an enum where every state represents a stage of execution. This enum is wrapped into a Future and driven by poll(). Execution resumes exactly where it stopped, using a match-based dispatch.
The critical detail: all variables alive across an await are stored inside the state. They are not dropped — they are preserved. If a large object exists at that point, it becomes part of the state machine until completion. The size of the future equals its largest state, even if rarely used.
Nested async calls make this worse. Each function embeds its own state machine, and sizes compound, not linearly but multiplicatively. Real-world cases show futures exceeding hundreds of kilobytes, which can break memory-constrained systems.
Execution is cooperative: no threads, no stack switching. The executor repeatedly polls the state machine, and a waker schedules it when I/O is ready. This is efficient — but only if state size is under control.
Python and Rust generators look similar but behave very differently. Python generators are heap-based and managed by the runtime. Rust generators are stackless, strictly typed, and require pinning to guarantee memory stability. Resume arguments are enforced at compile time, not runtime.
Async in Rust is effectively a generator wrapped as a Future. Yield maps to a pending state, completion maps to ready. This is not an abstraction — it is the actual implementation.
The main issue in production is state size growth. Since the largest state defines memory usage, deeply nested async flows can create oversized futures. The common fix is heap allocation via boxing, which limits size but adds overhead.
Generators are still unstable due to self-referential memory problems. Pin ensures safety but adds complexity. Stable alternatives include iterator-based patterns, macro-based generators, and upcoming language features designed to simplify this model.
Bottom line: Rust async is explicit state machines. If you ignore how state is built and stored, you risk hidden memory costs and performance issues.
Learn more on this page https://krun.pro/rust-generator-yield/ — the full deep-dive on Rust Generator yield: What the Compiler Actually Builds Under async/await
Top comments (0)