Reasoning About Async Rust with State Machines Async Rust converts every async function into a state machine that records where execution stopped and stores needed values, enabling efficient waiting without blocking threads. This internal model helps developers reason about async behavior, debug systematically, and design reliable code. Chapter 2 Reasoning About Async Rust with State Machines On AI assistance Discord https://discord.com/invite/cD9qEsSjUH and I'll work on it. Async Rust can be frustrating You write code that looks reasonable, the compiler disagrees, and the explanation does not seem connected to what you were trying to do. You change a few things, create a few new problems, and eventually get it working without really knowing why. And even when it compiles, you may hit a runtime issue where something hangs, never wakes up, or runs in an order you did not expect, and it is not always clear where to start debugging. This chapter starts building a model for reasoning about async Rust instead of treating it as a list of rules to memorize. We will develop that model throughout the chapters ahead, but making it instinctive will take time and practice. Here, the goal is to take the first step: understand how async Rust works internally and begin forming an intuition for why it behaves the way it does. With practice, this understanding will help you design reliable async code, debug it systematically, and reason about performance. Before we continue, let’s recap takeaways from Chapter 1 /posts/async-rust-chapter-1-hands-on-intro-to-async-rust/ : - Async work is represented by a future, and it does not run by itself. - Each poll moves the future forward until it finishes or has to wait. - After waiting, the future must continue from where it stopped. Problem with Waiting Programs spend a surprising amount of time waiting: for a database query, a network response, a timer, or a file operation. If a thread remains occupied during that wait, it cannot do anything else. Most of the time, the CPU is not doing useful work. The thread is simply waiting for an answer from somewhere else. Async code can give the thread back while it waits. The runtime can then use that same thread to move other work forward. Imagine a chat server, game server, or API may hold a network connection for every connected client, but most clients are not sending data at the same moment. Their connections spend most of their time waiting for the next message. Async lets a small number of threads work on whichever connections have data ready, instead of keeping one blocked thread waiting for every connected client. This does not make CPU-heavy work faster. A large calculation still needs CPU time. Async is most useful when work repeatedly alternates between doing a little computation and waiting on something outside the CPU. But giving the thread back creates a new problem: the waiting work must remember enough to continue later. Where did it stop? Which values does it still need? What should run when the answer arrives? Rust solves this by recording where the work stopped and storing the values it will need when it continues. When the work is ready again, Rust uses that saved information to resume from the right place instead of starting over. To do that, Rust turns every async fn into a state machine . Each state represents a place where the work can stop and stores what it needs to continue from there. State Machine A state machine describes work as a set of possible states and the events that move it between them. At any moment, the work is in one state. When something happens, it either stays there or moves to another. Think about an ATM. It might move through these states: Inserting a card moves the ATM from Idle to Card Inserted . Entering the correct PIN moves it to Authenticated . Choosing a withdrawal and an amount moves it forward again. The same input can mean different things in different states: pressing a number may enter a PIN in one state and choose an amount in another. Each state also stores what the next step needs. Card Inserted keeps the card details. Withdrawal Selected keeps the account and amount. Together, the current state and its stored values tell the ATM what has already happened and what it can do next. Now let’s see what this looks like in async Rust. We will use a small function that calculates the total price of two items in a shopping cart. The prices do not arrive immediately, so the function must get the socks price, then the shoes price, and finally add them together. php async fn cart total socks: Receiver