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

The Runtime: tokio

~11 min · async, tokio, runtime

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

Rust's standard library defines the Future trait and the async/await syntax — but deliberately ships no async runtime. To actually run Futures, you bring a runtime crate. The dominant one is tokio.

Why no built-in runtime?

Different workloads want different schedulers — a web server, an embedded device, and a GUI app have different needs. So Rust standardizes the language feature (Futures, await) but leaves the executor to the ecosystem. It's the same philosophy as the rest of the language: a minimal core, with policy decisions left to libraries you choose.

tokio: the de facto runtime

tokio provides the executor that polls your Futures, plus async versions of I/O — TCP, files, timers, channels. The #[tokio::main] attribute turns your async fn main into a regular main that sets up the runtime and blocks on your top-level Future. From there, every .await inside runs on tokio's scheduler.

std defines async; the runtime crate executes it. The language gives you Future and await; tokio (or async-std, smol) gives you the executor that drives them and the async I/O to await on. This split is why you add tokio to Cargo.toml and annotate main with #[tokio::main] before any async actually runs.

The ecosystem assumes tokio

Most async libraries — web frameworks like axum, database drivers like sqlx, HTTP clients like reqwest — are built on tokio. While alternatives exist, tokio is the safe default and what you'll encounter most. Most production Rust services run on it.

Code

#[tokio::main] gives you an async main·rust
// Cargo.toml:  tokio = { version = "1", features = ["full"] }

// #[tokio::main] sets up the runtime and runs the top-level Future
#[tokio::main]
async fn main() {
    let result = compute().await; // now .await actually runs
    println!("result: {result}");
}

async fn compute() -> i32 {
    // a real program would .await I/O here (network, file, timer)
    40 + 2
}

External links

Exercise

Set up a binary with tokio in Cargo.toml and #[tokio::main] on an async main. Write an async function that returns a computed value, await it from main, and print the result. Then use tokio::time::sleep(...).await to simulate an I/O wait — notice the program stays responsive rather than blocking a thread.
Hint
Add tokio = { version = "1", features = ["full"] }, annotate main with #[tokio::main], and now .await works at the top level. tokio::time::sleep is the async-friendly way to wait — it yields instead of blocking the thread.

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.