Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Async Runtime and When to Use Async

Rule

Document whether the project is sync or async before broad changes, and use Tokio when async is chosen.

Why

Async affects signatures, tests, dependencies, runtime ownership, and shutdown across a codebase rather than at one isolated call site.

Activation

Apply this page when setting project posture, adding the first async dependency, or reviewing runtime boundaries.

Do

  • Document whether the project is sync or async.
  • Record where async belongs and which Tokio features, test macros, and runtime conventions apply.
  • Use async for network I/O, timers, streaming, subprocess orchestration, and concurrent service work.
  • Keep parsing, validation, formatting, and bounded CPU-local transforms synchronous.
  • Let applications own runtime creation.
  • Document a reusable library’s runtime assumptions.
  • Follow task lifecycle, cancellation, and concurrency for implementation details.

Avoid

  • Do not convert a module to async solely because its caller is async.
  • Do not create a runtime inside a reusable library.
  • Do not build runtime abstraction when all real callers use Tokio.
  • Do not maintain parallel sync and async APIs without two real caller requirements.
  • Do not make tests async unless the behavior needs it.

Library vs Application

Applications own the runtime. Libraries may expose Tokio-based APIs when async behavior is central, but runtime and lifecycle assumptions remain visible.

Example

#![allow(unused)]
fn main() {
pub async fn handle_request(
    request: Request,
    client: &ApiClient,
) -> Result<Response, Error> {
    let command = parse_command(&request)?;
    let record = client.fetch_record(command.record_id()).await?;
    Ok(render_response(record))
}

fn parse_command(request: &Request) -> Result<Command, Error> {
    Command::try_new(request.path(), request.query())
}
}

Exceptions

  • Prefer a sync posture for local, CPU-bound, or short-lived tools.
  • Add runtime abstraction only for real callers on multiple runtimes.
  • Provide a sync wrapper only on a clearly synchronous application path where nested-runtime panics are impossible.