Course contentsShow
← Rust
Lesson 1235 of 1,91011. Asynchronous ProgrammingFree lesson

The Mechanics of State Machine Generation

Analyze how the compiler transforms async blocks into state-holding enums across await points.

In this lesson, you will learn how the Rust compiler transforms your async blocks into complex, state-holding enums to manage execution across suspension points.

From Syntax to State Machine

When you write an async block, the compiler doesn't just "run" it. Instead, it performs a transformation called CPS (Continuation-Passing Style) transformation, effectively converting your code into a Future implementation.

Crucially, the compiler identifies every .await point as a potential "yield" location. Since the function must pause and resume exactly where it left off, it needs to save its local variables. The compiler generates an anonymous enum that acts as the state machine for your task.

The Generated Enum

Imagine you have this simple async block:

async fn process_data(data: Vec<u8>) -> usize {
    let header = read_header(&data).await; // Suspension point
    let body = read_body(&data).await;     // Suspension point
    header.len() + body.len()
}

The compiler transforms this into an enum structure roughly like this:

enum ProcessDataFuture {
    Initial { data: Vec<u8> },
    WaitingForHeader { data: Vec<u8>, header: Header },
    WaitingForBody { data: Vec<u8>, header: Header, body: Body },
    Done,
}

Each variant represents a stage in the execution. When you call .poll(), the generated Future::poll implementation acts as a massive match statement. If it’s in Initial state, it attempts to execute read_header. If that returns Poll::Pending, it transitions the enum to WaitingForHeader and stores the current data and the pending future inside that variant.

Why this matters

By storing variables as fields within the enum variants, Rust ensures that data survives across await points even though the function's stack frame is popped. This is why the variables captured in an async block must be 'static or satisfy specific lifetime requirements: they are being moved out of the temporary stack frame and into the heap-allocated (or pin-allocated) state machine.

Key Takeaway: The compiler converts async functions into state-machine enums where every .await point marks a transition, and local variables are "promoted" into enum variants to persist across task suspension.