Reasoning About Async Rust with State Machines

febin1 pts0 comments

Reasoning About Async Rust with State Machines | AIBodh

On AI assistance<br>"Bodh" in "AIBodh" means understanding. Since conversational AI first arrived, the main thing I kept coming back to it for was learning, asking, getting it wrong, asking again, until something finally made sense. That turned into a belief I still hold: used well, AI can genuinely help people learn. So I use it for teaching, but not as a shortcut. Each article still takes me 20 to 25 hours. AI is how I get through the parts of writing that are genuinely hard: finding the example where a concept is the obvious answer, the analogy that clicks, the visual that makes it concrete, the pass that cuts the jargon and the bloat. I verify the code runs, I keep the voice mine, and I cut anything that does not help you understand. The judgment and the corrections stay with me. If anything feels off in any section, let me know on Discord 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.

Progress

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:

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.

Code...

work async state rust waiting from

Related Articles