Async Runtimes
Libraries for executing asynchronous Rust code.
tokio
The most popular async runtime for Rust.
[dependencies]
tokio = { version = "1", features = ["full"] }
Basic Usage
#[tokio::main]
async fn main() {
println!("Hello, async world!");
// Spawn a task
let handle = tokio::spawn(async {
// Async work
42
});
let result = handle.await.unwrap();
}
Features
| Feature | Purpose |
|---|---|
rt |
Basic runtime |
rt-multi-thread |
Multi-threaded runtime |
macros |
#[tokio::main], #[tokio::test] |
io-util |
I/O utilities |
net |
TCP/UDP networking |
time |
Timers and delays |
sync |
Channels, mutexes |
fs |
Async filesystem |
full |
All features |
Spawning Tasks
use tokio::task;
// Spawn async task
let handle = tokio::spawn(async {
expensive_async_work().await
});
// Spawn blocking task (for CPU-bound work)
let result = task::spawn_blocking(|| {
expensive_cpu_work()
}).await?;
// Run on current thread
let local = task::LocalSet::new();
local.run_until(async {
task::spawn_local(async { /* ... */ }).await
}).await;
Channels
use tokio::sync::{mpsc, oneshot, broadcast, watch};
// Multi-producer, single-consumer
let (tx, mut rx) = mpsc::channel(32);
tx.send("message").await?;
let msg = rx.recv().await;
// One-shot (single value)
let (tx, rx) = oneshot::channel();
tx.send("done")?;
let result = rx.await?;
// Broadcast (multi-consumer)
let (tx, mut rx1) = broadcast::channel(16);
let mut rx2 = tx.subscribe();
// Watch (latest value)
let (tx, rx) = watch::channel("initial");
tx.send("updated")?;
Time
use tokio::time::{sleep, timeout, interval, Duration};
// Sleep
sleep(Duration::from_secs(1)).await;
// Timeout
match timeout(Duration::from_secs(5), long_operation()).await {
Ok(result) => println!("Completed: {:?}", result),
Err(_) => println!("Timed out"),
}
// Interval
let mut interval = interval(Duration::from_millis(100));
loop {
interval.tick().await;
// Runs every 100ms
}
Synchronization
use tokio::sync::{Mutex, RwLock, Semaphore};
// Async mutex
let data = Mutex::new(0);
{
let mut lock = data.lock().await;
*lock += 1;
}
// Read-write lock
let data = RwLock::new(vec![]);
let read = data.read().await;
let mut write = data.write().await;
// Semaphore
let semaphore = Semaphore::new(3);
let permit = semaphore.acquire().await?;
async-std
Alternative async runtime with std-like API.
[dependencies]
async-std = { version = "1", features = ["attributes"] }
use async_std::prelude::*;
use async_std::task;
#[async_std::main]
async fn main() {
let handle = task::spawn(async {
42
});
let result = handle.await;
}
async-std Features
use async_std::{fs, net, channel, sync};
// File I/O
let content = fs::read_to_string("file.txt").await?;
// Networking
let stream = net::TcpStream::connect("127.0.0.1:8080").await?;
// Channels
let (tx, rx) = channel::bounded(10);
// Timeout
use async_std::future::timeout;
timeout(Duration::from_secs(5), async_operation()).await?;
smol
Minimal async runtime.
[dependencies]
smol = "2"
use smol::{block_on, spawn};
fn main() -> Result<(), Box<dyn std::error::Error>> {
block_on(async {
let task = spawn(async { 1 + 2 });
let result = task.await;
println!("Result: {}", result);
Ok(())
})
}
futures
Core async utilities (runtime-agnostic).
[dependencies]
futures = "0.3"
use futures::{future, stream, StreamExt, FutureExt};
// Join multiple futures
let (a, b, c) = future::join3(fut1, fut2, fut3).await;
// Select first to complete
let result = future::select(fut1, fut2).await;
// Try join (fail fast)
let (a, b) = future::try_join(fut1, fut2).await?;
// Stream processing
let results: Vec<_> = stream::iter(items)
.map(|x| async move { process(x).await })
.buffer_unordered(10)
.collect()
.await;
Comparison
| Runtime | Size | Features | Best For |
|---|---|---|---|
| tokio | Large | Full-featured | Production apps |
| async-std | Medium | Std-like API | Familiar API |
| smol | Small | Minimal | Embedded, small apps |
Choosing a Runtime
| Use Case | Recommendation |
|---|---|
| Web servers | tokio |
| General async | tokio or async-std |
| Minimal dependencies | smol |
| Library development | futures (runtime-agnostic) |
Runtime-Agnostic Code
// Use futures traits for compatibility
use futures::{AsyncRead, AsyncWrite};
async fn copy<R, W>(reader: R, writer: W) -> std::io::Result<u64>
where
R: AsyncRead + Unpin,
W: AsyncWrite + Unpin,
{
futures::io::copy(reader, writer).await
}
Summary
| Crate | Purpose |
|---|---|
| tokio | Full-featured async runtime |
| async-std | Std-like async runtime |
| smol | Minimal async runtime |
| futures | Async utilities and traits |