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