About rust-async-task-design
A Rust coding skill for designing and reviewing async task behavior, including ownership, suspension, blocking work, and cancellation. It gives developers a workflow and review checklist for making concurrency decisions explicit.
Key features
- 01Trace suspension points and held state
- 02Manage spawned task ownership and handles
- 03Isolate blocking and CPU-heavy work
- 04Review locks and borrows across await
- 05Check cancellation and failure paths
Use cases
- 01Reviewing Tokio task spawning
- 02Finding locks held across await
- 03Separating blocking work from async tasks
- 04Testing timeout and cancellation behavior
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ lvtd-llc/ skills --skill rust-async-task-designAfter installing, type/rust-async-task-designin Claude Code to confirm it loaded.
Bundled filesagents/openai.yamlreferences/async-runtime-patterns.md
Details
From the project READMERust Async Task Design
Use this skill to make Rust async code explicit about task ownership, suspension points, blocking work, and cancellation. Async is a concurrency model, not a default replacement for threads or a performance guarantee.
Core Workflow
- Identify the async boundary: runtime entrypoint, handler, client call, stream, background task, or trait method.
- Mark every
.awaitas a possible suspension point. Check what state is held across it. - Use
tokio::spawn(async move { ... })only when the task can own everything it needs and its future isSend + 'staticon the selected runtime. - Keep
JoinHandles when task success, panic, cancellation, or shutdown matters. Do not silently detach important work. - Keep blocking IO and CPU-heavy work out of async tasks. Use async APIs,
spawn_blocking, Rayon, or dedicated threads as the workload requires. - Do not hold a synchronous
MutexGuard,RefCellborrow, or non-Sendvalue across.await. - Add tests for timeout, cancellation, dropped channel, and failed task paths when those outcomes affect behavior.
Runtime Rules
Read references/async-runtime-patterns.md when reviewing task spawning,
shared state in async code, or blocking work.
- Use
std::sync::Mutexin async code only for short, low-contention critical sections that do not cross.await. - Use
tokio::sync::Mutexwhen a lock must be held across.await, but first consider moving the state behind a task and interacting by messages. - Use
spawn_blockingfor blocking operations that eventually finish. Limit parallelism for CPU-heavy work; Tokio's blocking pool can grow large. - Use
tokio::time::timeoutor explicit cancellation tokens for bounded operations. - Use
select!carefully: dropping a future can cancel it. Check cancellation safety before using it around IO or partially completed work.
Async Trait Rules
- Native
async fnin traits is fine for private traits and static dispatch when callers do not need to add bounds to the returned future. - Public traits that need
Sendfutures should usually spell the method asfn name(...) -> impl Future<Output = T> + Sendor usetrait-variantto offer local andSendvariants. - Public traits that need dynamic dispatch usually need an explicit boxed
future return type, such as
Pin<Box<dyn Future<Output = T> + Send + '_>>. Useasync-traitas an ergonomics layer only when the dependency and allocation tradeoff are acceptable. - Avoid leaking executor-specific types from a trait unless the trait is intentionally runtime-specific.
Review Checklist
- No lock or borrow crosses
.awaitaccidentally. - Spawned tasks own their inputs with
async move. - Important
JoinHandles are awaited, monitored, or aborted on shutdown. - Blocking work is isolated and bounded.
- Error paths preserve context instead of becoming
JoinErroror timeout noise.