Rust 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
.await as 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 is Send + 'static on 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, RefCell borrow, or non-Send value
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::Mutex in async code only for short, low-contention critical
sections that do not cross .await.
- Use
tokio::sync::Mutex when a lock must be held across .await, but first
consider moving the state behind a task and interacting by messages.
- Use
spawn_blocking for blocking operations that eventually finish. Limit
parallelism for CPU-heavy work; Tokio's blocking pool can grow large.
- Use
tokio::time::timeout or 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 fn in 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
Send futures should usually spell the method as
fn name(...) -> impl Future<Output = T> + Send or use trait-variant to
offer local and Send variants.
- Public traits that need dynamic dispatch usually need an explicit boxed
future return type, such as
Pin<Box<dyn Future<Output = T> + Send + '_>>.
Use async-trait as 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
.await accidentally.
- 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
JoinError or timeout noise.
Source: hashgraph-online/awesome-codex-plugins → plugins/LVTD-LLC/skills/skills/rust-async-task-design/SKILL.md
1---2name: rust-async-task-design3description: Design and review Rust async code around task ownership, suspension points, cancellation, blocking work, and runtime boundaries. Use when writing, refactoring, or reviewing futures, async functions, Tokio tasks, spawn, JoinHandle, Send futures, async traits, cancellation, or mutexes across await points.4---567# Rust Async Task Design89Use this skill to make Rust async code explicit about task ownership, suspension10points, blocking work, and cancellation. Async is a concurrency model, not a11default replacement for threads or a performance guarantee.1213## Core Workflow14151. Identify the async boundary: runtime entrypoint, handler, client call,16 stream, background task, or trait method.172. Mark every `.await` as a possible suspension point. Check what state is held18 across it.193. Use `tokio::spawn(async move { ... })` only when the task can own everything20 it needs and its future is `Send + 'static` on the selected runtime.214. Keep `JoinHandle`s when task success, panic, cancellation, or shutdown22 matters. Do not silently detach important work.235. Keep blocking IO and CPU-heavy work out of async tasks. Use async APIs,24 `spawn_blocking`, Rayon, or dedicated threads as the workload requires.256. Do not hold a synchronous `MutexGuard`, `RefCell` borrow, or non-`Send` value26 across `.await`.277. Add tests for timeout, cancellation, dropped channel, and failed task paths28 when those outcomes affect behavior.2930## Runtime Rules3132Read `references/async-runtime-patterns.md` when reviewing task spawning,33shared state in async code, or blocking work.3435- Use `std::sync::Mutex` in async code only for short, low-contention critical36 sections that do not cross `.await`.37- Use `tokio::sync::Mutex` when a lock must be held across `.await`, but first38 consider moving the state behind a task and interacting by messages.39- Use `spawn_blocking` for blocking operations that eventually finish. Limit40 parallelism for CPU-heavy work; Tokio's blocking pool can grow large.41- Use `tokio::time::timeout` or explicit cancellation tokens for bounded42 operations.43- Use `select!` carefully: dropping a future can cancel it. Check cancellation44 safety before using it around IO or partially completed work.4546## Async Trait Rules4748- Native `async fn` in traits is fine for private traits and static dispatch49 when callers do not need to add bounds to the returned future.50- Public traits that need `Send` futures should usually spell the method as51 `fn name(...) -> impl Future<Output = T> + Send` or use `trait-variant` to52 offer local and `Send` variants.53- Public traits that need dynamic dispatch usually need an explicit boxed54 future return type, such as `Pin<Box<dyn Future<Output = T> + Send + '_>>`.55 Use `async-trait` as an ergonomics layer only when the dependency and56 allocation tradeoff are acceptable.57- Avoid leaking executor-specific types from a trait unless the trait is58 intentionally runtime-specific.5960## Review Checklist6162- No lock or borrow crosses `.await` accidentally.63- Spawned tasks own their inputs with `async move`.64- Important `JoinHandle`s are awaited, monitored, or aborted on shutdown.65- Blocking work is isolated and bounded.66- Error paths preserve context instead of becoming `JoinError` or timeout noise.6768---6970**Source:** [`hashgraph-online/awesome-codex-plugins`](https://github.com/hashgraph-online/awesome-codex-plugins) → `plugins/LVTD-LLC/skills/skills/rust-async-task-design/SKILL.md`