Async I/O, Coordination Primitives & Servers
Racing Futures with select!
use tokio::time::{timeout, Duration};
tokio::select! {
res = fetch_from_primary() => println!("primary: {res:?}"),
res = fetch_from_backup() => println!("backup: {res:?}"),
}
// Bounding how long a call is allowed to take
let res = timeout(Duration::from_secs(2), slow_operation()).await;select! polls multiple futures concurrently and proceeds with whichever completes first, cancelling the rest — used for racing redundant sources, or (as timeout builds on internally) enforcing a deadline on an operation.
Sharing State Safely Across Tasks
use std::sync::Arc;
use tokio::sync::Mutex;
let counter = Arc::new(Mutex::new(0));
for _ in 0..10 {
let counter = Arc::clone(&counter);
tokio::spawn(async move {
let mut n = counter.lock().await;
*n += 1;
});
}Arc gives shared ownership across tasks; tokio::sync::Mutex (not std::sync::Mutex) yields to the scheduler while waiting for the lock instead of blocking a worker thread — important because holding a std Mutex guard across an .await point can stall other tasks.
Channels for Task Coordination
mpsc lets many producer tasks send values to one consumer — the standard way to hand off data without shared mutable state. oneshot sends exactly one value to exactly one waiting receiver, often used for a result or shutdown signal. broadcast fans a value out to every current subscriber, for pub/sub-style notification.
Building Network Servers
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
loop {
let (socket, _) = listener.accept().await?;
tokio::spawn(async move {
handle_connection(socket).await;
});
}TcpListener is Tokio's async, non-blocking equivalent of the standard library's blocking listener. Spawning a task per accepted connection is why Tokio-based servers (Axum, Hyper, Tonic all build on it) scale to very high connection counts more cheaply than a thread-per-connection model.
Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free