Tokio
01 / 02

The Async Runtime: Futures, Tasks & Scheduling

The Async Runtime: Futures, Tasks & Scheduling

Why Rust Needs a Runtime Crate

Rust's standard library defines async/await syntax and the Future trait, but ships no executor — futures are lazy state machines that do nothing until something polls them. Tokio is the de facto executor: it provides the reactor (non-blocking I/O), a task scheduler, and timers that make async code actually run.

Entry Point & Spawning Tasks

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        // runs concurrently as its own lightweight task
        fetch_data().await
    });

    let result = handle.await.unwrap();
    println!("{result}");
}

#[tokio::main] generates the boilerplate to build a runtime and run an async main inside it (Rust's real main can't be async). tokio::spawn hands a future to the scheduler as an independent task, multiplexed onto a small pool of OS threads — thousands of tasks can run concurrently far more cheaply than one-thread-per-task.

Don't Block the Executor

// BAD: a tight CPU-bound loop starves every other task on this worker thread
async fn compute() -> u64 {
    (0..1_000_000_000).sum()
}

// GOOD: offload blocking/CPU-heavy work to a dedicated thread pool
let result = tokio::task::spawn_blocking(|| {
    (0..1_000_000_000u64).sum::<u64>()
}).await.unwrap();

Tokio's cooperative scheduler assumes tasks yield at .await points. A long CPU-bound computation with no yields blocks the whole worker thread; spawn_blocking moves it to a separate pool meant for exactly this, keeping async workers free.

Runtime Flavors

The default multi-threaded runtime distributes tasks across a worker pool for real parallelism. #[tokio::main(flavor = "current_thread")] runs everything on a single OS thread instead — lower overhead for lightly concurrent or single-threaded workloads, at the cost of no cross-core task parallelism.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free