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.