Who Runs Your Rust Future? Hands-On Intro to Async Rust

febin1 pts0 comments

Who Runs Your Rust Future? Hands-On Intro to Async Rust | AIBodh

On AI assistance<br>Yes, AI assistance was involved in writing this chapter. I worked on the structure, the technical decisions, the approach, how to structure the code, and put together a list of anticipated questions learners would have. AI helped expand on the structure and explanations, and I edited throughout. In total, I spent around 20–25 hours on each chapter, between coding and writing. If anything feels off in any section, let me know on Reddit or Discord and I'll work on it.

Most async Rust tutorials send you down one of two roads, and both dead-end in the same spot. Read the async book and you will learn Future, poll, and Pin, and even hand-build a toy executor, then still not know how to ship anything real. Just reach for Tokio and your code works, but Tokio is a black box, so the first strange error leaves you guessing at what is actually running it.

This series takes the road between them. You build the engine yourself, the future, the waker, the executor, until none of it is magic, and then you drive the real one, Tokio, and recognize every part because you already built it. It starts with the smallest, most stubborn question in async Rust: who actually runs your async code? Or, in Rust’s own terms: who runs your future?

This series assumes two things. First, that you’ve written async and await code in JavaScript before. Second, that you’re comfortable with Rust’s basics: structs, enums, associated functions, and closures (all covered in the free chapters of The Impatient Programmer’s Guide to Bevy and Rust).

A quick note on the series: I plan to keep at least 5 to 8 chapters free to read here, with the rest collected in a paid ebook. The free chapters may not follow the numbering in order, so some will land out of sequence.

Future

If you’ve shipped any modern JavaScript, you’ve done this a hundred times:

Pseudocode, don't use.<br>async function getUser() { /* ... */ }const user = await getUser(); // and it just... works

You write async function, sprinkle in some await, and it runs. You never had to think about what runs it, because something always did. The node process ships a built-in event loop : a hidden engine that picks up your Promises and pushes each one forward until it’s done. The loop is always there, behind the curtain, doing the work for you.

Rust does the opposite, on purpose. It ships no event loop at all. Nothing in the language is sitting around waiting to run your async code. If you want one, you bring it in. Or, the way we’re going to learn it, you build one yourself.

A second job

That sounds like a missing feature. By the end of this series it’ll feel like the opposite. But before we ask who runs it, let’s be precise about what async is.

A normal function is the kind you write every day. You call it, it runs top to bottom right then and there, and by the next line its return value is already sitting in your variable:

A normal functionYou call it, it runs immediately, and hands back the finished value.fn add(a: i32, b: i32) -> i32 { a + b }let sum = add(2, 3);+Runs right now. sum is 5 before the next line even starts.

An async function doesn’t do that. You call it and it hands you back a box instead of a result. Not the value, but a thing that represents a value that isn’t ready yet, stamped “I’ll be done later.” Every async language has this box; they just use different names for it:

JavaScript calls it a Promise .

Rust calls it a Future .

Different words, identical idea: a value that represents work that isn’t finished yet.

Hang on, doesn’t JavaScript have a Future too?

Not as a type. You’ll still hear both words, and they’re not interchangeable: a promise is the write side, filled with the value once the work finishes; a future is the read side, the handle you hold and await.

resolve is the write side; the Promise you await is the read side.<br>The write sideCode doing the work calls resolve to put the value in, when it is ready.const promise = new Promise((resolve) => { setTimeout(() => resolve("the data"), 1000);+Value goes IN here, one second later.});The read sideYou await the promise itself. The event loop runs it; await just hands you the value.const data = await promise;+Value comes OUT here, with no work from you.

Rust names the handle you hold a Future, because in Rust you are the one who reads it, by polling, as you’ll see in a moment. So whenever you read Future in this series, picture a Promise that you have to read yourself.

In Node, the instant you call an async function, the box is live . The hidden event loop grabs it and drives it to completion whether you’re watching or not. await is just you asking for the value once it’s ready. The work was always going to happen.

In Rust, calling an async fn does nothing :

Calling it does nothingIn Rust an async fn call builds a Future and stops. The body has not run.async fn get_user() -> User { /* ... */ }let f = get_user();+f is a...

async rust future runs value await

Related Articles