C.W.K.
Stream
Lesson 02 of 05 · published

async, await & Futures

~11 min · async, await, future

Level 0Rust Curious
0 XP0/80 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete

async and .await are the two halves of the syntax. Together they let you write code that looks sequential but yields the thread at each waiting point.

async makes a Future

An async fn doesn't run when you call it — it returns a Future, a value representing 'work that will produce a result later.' The body doesn't execute until something drives the Future to completion. This is the surprise for newcomers: calling an async function does nothing on its own.

.await drives and yields

.await on a Future does two things: it drives that Future toward completion, and — crucially — if the Future isn't ready (still waiting on I/O), it yields the current thread so other tasks can run, resuming later when the value is ready. The code reads top-to-bottom like blocking code, but under the hood each .await is a potential yield point.

An async fn returns a Future that does nothing until awaited. Calling it just builds the Future; .await (or a runtime) executes it. 'Why is my async function not running?' almost always means you built the Future but never awaited or spawned it.

Futures are lazy and zero-cost

Rust's Futures are lazy (they do nothing until polled) and compile to efficient state machines — no heap allocation per await, no hidden runtime in the language itself. The compiler turns your async fn into a state machine the runtime polls. That's why async is zero-cost: you don't pay for a garbage-collected event loop baked into the language.

Code

async fns compose; await drives them·rust
// async fn returns a Future; .await drives it and yields while waiting
async fn step_one() -> i32 { 1 }
async fn step_two(x: i32) -> i32 { x + 1 }

async fn pipeline() -> i32 {
    let a = step_one().await;  // drive step_one, get 1
    let b = step_two(a).await; // drive step_two, get 2
    b
}

// pipeline() returns a Future. It runs only when a runtime awaits it —
// the next lesson supplies that runtime (tokio).
fn main() {
    let _future = pipeline(); // built, not yet run
    println!("future created, not yet executed");
}

External links

Exercise

Write two async fns where the second takes the first's result. Compose them in a third async fn using .await. Then call that third function in main WITHOUT awaiting (you can't await in a plain main yet) and observe the 'unused Future' warning. What does the warning tell you about laziness?
Hint
The warning fires because a Future does nothing until driven. You built the work but never awaited it. The next lesson adds #[tokio::main] so you can actually .await at the top level.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.