Tokio Basics
9 examples to get you started with Tokio - 6 basic and 3 intermediate.
Prerequisites
- Rust 1.97.0 (edition 2024).
tokio = { version = "1", features = ["full"] }inCargo.toml.
Basic Examples
1. #[tokio::main]
The attribute installs a multi-thread runtime and drives main.
#[tokio::main]
async fn main() {
println!("Tokio is running");
}- Expands to
Runtime::new()+block_on. - Default flavor is multi-threaded.
- Use
async fn mainonly with a runtime attribute or manualblock_on.
Related: Executors & Runtimes - runtime internals
2. Spawning Tasks
tokio::spawn schedules independent work on the runtime.
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
100
});
println!("{}", handle.await.unwrap());
}- Returns
JoinHandle<T>- a future you await for the result. - Spawned tasks must be
'staticandSend(multi-thread runtime). - Panics in tasks propagate as
JoinErroron await.
Related: Tasks & JoinHandles - lifecycle
3. Async Sleep
Cooperative delays without blocking threads.
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
println!("start");
sleep(Duration::from_millis(100)).await;
println!("end");
}sleepyields the worker thread to other tasks.- Requires Tokio time driver enabled.
- Prefer over
std::thread::sleepin async code.
Related: Timers & Intervals - timers
4. TcpListener Echo
Tokio provides async networking primitives.
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;
#[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; 32];
let n = socket.read(&mut buf).await.unwrap();
socket.write_all(&buf[..n]).await.unwrap();
});
let mut stream = tokio::net::TcpStream::connect(addr).await?;
stream.write_all(b"ping").await?;
Ok(())
}bindandacceptare async - no blocked threads waiting.- Each connection typically gets its own spawned task.
- Axum builds on Tokio's networking stack.
Related: Async I/O - full I/O guide
5. mpsc Channel
Tokio channels are async-aware.
use tokio::sync::mpsc;
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(4);
tx.send(1).await.unwrap();
tx.send(2).await.unwrap();
drop(tx);
while let Some(n) = rx.recv().await {
println!("{n}");
}
}- Bounded capacity applies backpressure on
send().await. recv().awaitwaits without blocking OS threads.- Drop all senders to close the channel.
Related: Channels in Tokio - all channel types
6. spawn_blocking
Offload blocking syscalls from worker threads.
#[tokio::main]
async fn main() {
let len = tokio::task::spawn_blocking(|| {
std::fs::metadata("/etc/hosts").map(|m| m.len())
})
.await
.unwrap()
.unwrap();
println!("{len}");
}- Runs on Tokio's blocking thread pool.
- Essential for legacy sync APIs in async servers.
- Prefer
tokio::fswhen async file API suffices.
Related: Blocking in Async - when and how
Intermediate Examples
7. select! Multiplexer
Race multiple async operations.
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
tokio::select! {
_ = sleep(Duration::from_millis(50)) => println!("timer"),
_ = async { sleep(Duration::from_millis(10)).await } => println!("fast"),
}
}- First completed branch wins; others are cancelled.
- Common for shutdown signals + work loops.
- Read cancel-safety docs for each branch type.
Related: select! & Concurrency - patterns
8. Axum-Style Shared State
Arc clones cheaply into handlers and tasks.
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
label: Arc<String>,
}
#[tokio::main]
async fn main() {
let state = AppState {
label: Arc::new("api".into()),
};
let task_state = state.clone();
tokio::spawn(async move {
println!("{}", task_state.label);
})
.await
.unwrap();
}CloneonAppStateclonesArcpointers, not strings.- Store pools and clients in state at startup.
- See Shared State page for mutex vs actor patterns.
Related: Shared State in Async - patterns
9. Graceful Shutdown Sketch
Listen for ctrl-c and drain tasks.
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
tokio::select! {
_ = tokio::signal::ctrl_c() => println!("shutting down"),
_ = async {
tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
} => {}
}
Ok(())
}ctrl_c()integrates with Tokio signal handling.- Production servers drain in-flight requests before exit.
- Use
CancellationTokenfor child task trees.
Related: Graceful Shutdown - production guide
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+.