tokio
Tokio is the de facto async runtime for Rust network services. It provides an executor, timers, async IO, and synchronization primitives built for async/await.
Recipe
[dependencies]
tokio = { version = "1", features = ["full"] }#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
42
});
let n = handle.await.unwrap();
assert_eq!(n, 42);
}When to reach for this:
- HTTP servers (
axum,hyper) - Database pools (
sqlx) - Long-lived daemons with concurrent connections
Working Example
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
tokio::spawn(async move {
let (mut socket, _) = listener.accept().await.unwrap();
let mut buf = [0u8; 4];
socket.read_exact(&mut buf).await.unwrap();
socket.write_all(b"pong").await.unwrap();
});
let mut client = tokio::net::TcpStream::connect(addr).await?;
client.write_all(b"ping").await?;
let mut out = [0u8; 4];
client.read_exact(&mut out).await?;
assert_eq!(&out, b"pong");
Ok(())
}What this demonstrates:
#[tokio::main]sets up the multi-thread runtimespawnruns concurrent tasks- Async IO without blocking threads
Deep Dive
Runtime Features
| Feature set | Use |
|---|---|
rt-multi-thread | Production servers (default in full) |
rt | Single-threaded embedded or tests |
macros | #[tokio::main], #[tokio::test] |
time | sleep, interval, timeouts |
Cross-Link
See the Tokio basics section for channels, select!, graceful shutdown, and tracing integration.
Gotchas
- Blocking in async -
std::fs::readblocks the worker thread. Fix:tokio::task::spawn_blockingortokio::fs. - CPU-heavy work on runtime - starves IO. Fix: dedicated thread pool or
rayon. current_threadruntime surprises - tasks only run when polled on one thread. Fix: use multi-thread for servers.- Unbounded channels - memory grows under load. Fix: bounded channels with backpressure.
- Missing
enable_io- custom runtime builder without IO feature fails at accept. Fix: mirrorfullfeatures explicitly.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
async-std | Legacy codebases | Greenfield Tokio ecosystem crates |
smol | Lightweight runtime | Heavy Axum/sqlx stacks |
| Sync threads | CLI batch tools | Thousands of concurrent connections |
FAQs
Tokio 1.x stable?
Yes. Minor 1.x releases are semver-compatible. Pin 1 in Cargo.toml.
How many worker threads?
Default is CPU cores. Tune with worker_threads(n) on RuntimeBuilder for mixed workloads.
Can I mix sync and async?
Yes at boundaries: spawn blocking for sync libraries; keep async at the network edge.
Does Tokio replace threads?
No. Use threads for CPU parallelism; Tokio for concurrent IO waiting.
Related
- Tokio basics - full section
- reqwest & sqlx - async HTTP and DB
- tracing - async-aware logging
- Async / Tokio Skill - agent review patterns
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.