Async & Futures
async/await lets one thread juggle many tasks, each yielding at an .await point instead of blocking — a lighter alternative to an OS thread per task, and the foundation of Rust web servers.
Why Async
An OS thread costs memory and a context switch. Async instead runs many tasks on a few threads, suspending each task at an .await instead of blocking the whole thread. An executor polls a future, and a waker re-queues it when it can make progress:
Figure 1 — a future runs only when polled; if it is not ready it yields and the waker re-queues it.
async / await
Calling an async fn produces a future — a value describing work that has not run yet. Nothing happens until the future is awaited. An async function compiles into a state machine that resumes where it left off:
async fn fetch(name: &str) -> String {
format!("data for {name}")
}
async fn fetch_two() -> (String, String) {
let a = fetch("a").await; // yield here; resume when ready
let b = fetch("b").await; // then do the second
(a, b)
}
Executors & the tokio Runtime
The standard library provides the async syntax, but a runtime is needed to actually drive futures. tokio is the de-facto production runtime: its executor polls many tasks and its reactor wakes them when I/O completes:
[dependencies]
tokio = { version = "1", features = ["full"] }
use tokio::time::{sleep, Duration};
#[tokio::main] // the runtime starts here
async fn main() {
let a = async { // two tasks, run concurrently
sleep(Duration::from_millis(100)).await;
println!("task A done");
};
let b = async {
println!("task B done");
};
tokio::join!(a, b); // drive both to completion
}
Async versus Threads
Async shines when many tasks spend most of their time waiting (network, disk, timers); threads shine for CPU-bound work. Rust lets you mix both deliberately:
| Async (cooperative) | Threads (preemptive) |
|---|---|
| Many tasks on few threads | One task per OS thread |
Yields only at .await | Switched by the OS scheduler |
| Great for I/O-bound work | Great for CPU-bound work |
| Tiny per-task overhead | Larger per-thread stack/memory |