Tokio Basics
9 examples to get you started with Tokio - 6 basic and 3 intermediate.
Search across all documentation pages
9 examples to get you started with Tokio - 6 basic and 3 intermediate.
tokio = { version = "1", features = ["full"] } in Cargo.toml.The attribute installs a multi-thread runtime and drives main.
#[tokio::main]
async fn main() {
println!("Tokio is running");
}Runtime::new() + block_on.async fn main only with a runtime attribute or manual block_on.Related: Executors & Runtimes - runtime internals
tokio::spawn schedules independent work on the runtime.
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
100
});
println!("{}", handle.await.unwrap());
}JoinHandle<T> - a future you await for the result.'static and Send (multi-thread runtime).JoinError on await.Related: Tasks & JoinHandles - lifecycle
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");
}sleep yields the worker thread to other tasks.std::thread::sleep in async code.Related: Timers & Intervals - timers
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(())
}bind and accept are async - no blocked threads waiting.Related: Async I/O - full I/O guide
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}");
}
}send().await.recv().await waits without blocking OS threads.Related: Channels in Tokio - all channel types
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}");
}tokio::fs when async file API suffices.Related: Blocking in Async - when and how
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"),
}
}Related: select! & Concurrency - patterns
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();
}Clone on AppState clones Arc pointers, not strings.Related: Shared State in Async - patterns
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.CancellationToken for 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+.
Reviewed by Chris St. John·Last updated Jul 16, 2026