All Rust templates

Rust + Tokio Async Patterns

Tokio fundamentals: tasks, select!, channels, cancellation, join!.

DevZone Tools1,350 copiesUpdated Feb 13, 2026Rust
# CLAUDE.md — Rust + Tokio Async

## Setup

- `#[tokio::main]` on `main`. For libraries, accept a runtime handle from the caller — never assume one.
- Pin the runtime version in `Cargo.toml`. Major versions break compatibility.
- Default to the multi-thread runtime. Use the current-thread runtime only when you need single-thread guarantees.

## Tasks

- `tokio::spawn(async move { ... })` schedules a task on the runtime. Returns a `JoinHandle<T>`.
- Don't fire-and-forget unless you genuinely don't care. Hold the handle and `await` it, or `abort` it intentionally.
- Use `tokio::task::JoinSet` for fan-out:
  ```rust
  let mut set = JoinSet::new();
  for item in items { set.spawn(work(item)); }
  while let Some(res) = set.join_next().await { ... }
  ```

## Cancellation

- Cancellation is cooperative. Tasks are dropped at the next `await` point.
- Use `tokio::select!` to race futures, including a cancellation token:
  ```rust
  tokio::select! {
      res = work() => handle(res),
      _ = cancel.cancelled() => break,
  }
  ```
- `tokio_util::sync::CancellationToken` for structured cancellation across many tasks.

## Channels

- `tokio::sync::mpsc` (multi-producer, single-consumer) — most common.
- `tokio::sync::broadcast` for fan-out to multiple subscribers.
- `tokio::sync::oneshot` for "one-shot" reply patterns (request → response).
- `watch` for "current value" semantics where late receivers should see the latest.

## Sync primitives

- `tokio::sync::Mutex` — async-aware. `std::sync::Mutex` is fine when held briefly and not across `.await`.
- `RwLock` only when reads vastly outnumber writes. Otherwise `Mutex` is simpler.
- Don't hold any lock across `.await`. Either drop it explicitly (`drop(guard)`) or restructure.

## Timing

- `tokio::time::sleep` and `interval` for delays. Never `std::thread::sleep` in async — it blocks the runtime.
- `tokio::time::timeout(duration, fut)` to bound any await.
- `Instant::now` is fine in async code; the deltas are correct.

## Blocking work

- CPU-bound or sync I/O work goes in `tokio::task::spawn_blocking`. Returns a `JoinHandle<T>`.
- The blocking pool has a separate thread budget. Don't dump unlimited blocking work — it'll stall.
- For "blocking" libraries you can't avoid, wrap them in a small `spawn_blocking` shim.

## Streams

- `tokio_stream` and `futures::Stream` for async iteration.
- `StreamExt::buffered(n)` to bound concurrency in pipelines.
- Don't collect a stream into a `Vec` if you don't have to. Keep it streaming.

## Don't

- Don't call `.block_on(...)` from inside an async function. That's how you create deadlocks.
- Don't share a tokio Mutex across `.await` if a `parking_lot::Mutex` would do — `parking_lot` is faster but synchronous.
- Don't swallow `JoinError`. It carries panic info.
- Don't use `Rc` or `RefCell` across tasks. They're not `Send`. Use `Arc` + `Mutex`.

Other Rust templates