Overview
This is Rust's version of the runtime question the JVM answers with
competing VMs and Go answers with
one built-in scheduler: the
language defines async fn and Future, but ships no executor, no reactor, no
timers — a deliberate zero-cost stance that pushed the runtime into the library
ecosystem. The market answered decisively: Tokio is the de-facto standard, the
substrate under axum, tonic, sqlx, reqwest and most of the async ecosystem. The cost of
that decisiveness is the ecosystem's most persistent tension: libraries in practice
couple to a runtime, and writing runtime-agnostic async code remains genuinely hard.
Key points
- What the language provides:
Futureas a compiled state machine,async/awaitsyntax,Waker/Pollas the contract. What it doesn't: anything that runs a future — spawning, I/O event loops, timers, task scheduling all come from a crate. - Tokio is the default choice and the safe assumption: multi-threaded work-stealing
scheduler, mio-based reactor (epoll/kqueue/IOCP), timers,
tokio::syncprimitives,#[tokio::main]/#[tokio::test]entry points, plus the ecosystem: tower, hyper, tracing, tokio-console. Production posture, LTS releases, corporate backing. - smol is the composable counter-design: a federation of small crates (async-executor, async-io, async-channel, blocking) you can assemble piecemeal — attractive for libraries and small tools; its pieces underlie several runtime-agnostic crates.
- async-std is gone: officially discontinued in 2025 (its crates archived) — worth knowing because tutorials and older library feature flags still reference it; its ecosystem role folded into smol's crates and Tokio.
- embassy brings async to embedded: executors and HALs for microcontrollers where async replaces an RTOS — a genuinely different answer unavailable to Go/Java.
- The agnostic tension: a library doing I/O must pick a reactor, so "agnostic"
libraries either abstract via traits + feature flags (sqlx's
runtime-tokio, lapin), stick to executor-only needs (futures + async-channel), or just require Tokio (axum, tonic). There is noExecutortrait in std, and the "keyword generics"/pluggable runtime work remains exploratory — assume Tokio unless you have a reason not to. - Async's sharp edges live here too: cancellation-by-drop,
Sendbounds on spawned tasks, blocking-the-executor hazards (spawn_blockingfor CPU/blocking work), and observability via tokio-console and tracing.
Details
Comparison
| Runtime | Scheduler | Reactor | Sweet spot | Ecosystem pull |
|---|---|---|---|---|
| Tokio | Multi-threaded, work-stealing (current-thread optional) | mio (epoll/kqueue/IOCP); tokio-uring experiment | Network services, the default | Dominant — axum, tonic, sqlx, reqwest assume it |
| smol | async-executor, single or multi-thread | async-io (polling crate) | Small tools, composable libraries | Its component crates outlive the umbrella |
| embassy | Static, interrupt-driven executors | HAL-integrated | Embedded / no_std | The embedded-async standard |
| glommio | Thread-per-core, cooperative | io_uring | Storage/network engines wanting core affinity | Niche, influential design |
The Go/Java contrast, sharpened
Go bakes one scheduler into every binary and makes every function implicitly
async-capable — no colored functions, no runtime choice, no zero-cost claim. Java
(21+) retrofitted the same model as virtual threads on the one JVM. Rust chose
explicit async with no runtime so that embedded targets, kernels and WASM could use
the same language — and pays with the function-coloring split (sync vs async APIs) and
the library-coupling problem above. All three are coherent answers; Rust's is the only
one where "which runtime?" is a dependency-tree question — see
Concurrency and Parallelism
for the crossroads itself.
Related
- Rust compilers & the language definition — parent note; why the language ships no runtime at all.
- Deep dive Rust — async in the language's overall model:
state machines,
Pin, cancellation-by-drop. - Rust web frameworks — the tower/hyper stack built on Tokio.
- Profiling & debugging — tokio-console and tracing, the async observability answer.
- Deep dive Go — the built-in-scheduler counterpoint.
- Deep dive Java — virtual threads, the JVM's convergent answer.
- Hexagonal architecture reference implementation — Rust — where the runtime choice lives in the house layout: binary and infra crates, never the domain contract.
Citations
[1] Tokio documentation [2] async-std discontinuation notice [3] Async Rust book — the state of async