Guides advanced Rust async programming: Tokio vs async-std decision, async trait patterns, spawning strategies, blocking in async contexts, and structured concurrency with JoinSet.
SkillScore96Maintained
Automatic
◆Coding pack
Last verified 10/6/2026
What it changes
Passed 25 of 26 test cases with the skill, and 23 of 26 without it.
Without skill88%
With skill96%
Checks it passed that the model alone missed
Recommends offloading CPU-intensive image hashing to tokio::task::spawn_blocking.
Recommends exposing runtime-agnostic async fn or standard async I/O traits, reserving Tokio for dev-dependencies.
Explains that incompatible waker implementations between runtimes cause tasks to silently hang without polling again.
3 fixed22 passed either way0 still failing1 broke
Pass-rate lift
+8 pp
Tokens
+281%
Agent turns
0%
Response time
-9%
Model gemini-3.6-flashMeasured 10/6/2026v125 of 26 cases passed with skill
Tokens, turns and time describe resource use, not a dollar price.
See it in action
One of 3 test cases the skill fixed. Same model, same prompt, with and without the skill.
1 of 3
Prompt
I'm building a backend Rust service that reads jobs from an incoming queue, fetches user profiles from Postgres via SQLx, calls an external authentication endpoint, and hashes large image files before storing them. I need a comprehensive async architecture guide. Please structure your response with a use case classification, a runtime decision matrix table, a spawning strategy table, concrete implementation code (showing the main workflow, handling of CPU/blocking operations, and graceful shutdown logic), key trade-offs and risks, and an async testing strategy.
What was checked, without and with the skill
Checked
Without skill
With skill
Recommends offloading CPU-intensive image hashing to tokio::task::spawn_blocking.
Missed
Passed
Recommends selecting Tokio as the runtime for SQLx and service execution.
Passed
Passed
case-01 · gemini-3.6-flash · 10/6/2026 · answers are capped and redacted
What's inside
The instructions your model reads when the skill loads.
SKILL.md588 linessingle file
Rust Async Patterns
When to Use
Use this skill when the user:
Asks which async runtime to choose for a Rust project (Tokio vs async-std vs smol) and needs a decision framework based on ecosystem maturity, feature set, and compatibility
Wants to implement async traits in Rust and encounters the "async fn in trait" limitation or needs to understand when to use async-trait crate vs native async fn in trait (stabilized in Rust 1.75+)
Needs to spawn tasks and must decide between tokio::spawn, tokio::task::spawn_local, JoinSet, FuturesUnordered, or join! based on lifetime, ownership, and cancellation requirements
Is calling blocking code (database drivers using std I/O, CPU-intensive computation, FFI with blocking syscalls) from within an async context and risks starving the executor
Wants structured concurrency -- bounding task lifetimes, collecting results from concurrent subtasks, or implementing fan-out/fan-in patterns using JoinSet or FuturesUnordered
Is debugging async issues: dropped futures, missed .await calls, accidental blocking on the async thread pool, or tasks leaking after cancellation
Needs to design async streams, implement custom Future by hand, or use Pin<Box<dyn Future>> for dynamic dispatch of futures
Asks about channels in async contexts: tokio::sync::mpsc, broadcast, oneshot, watch, or how to choose between them
Do NOT use this skill when:
The user asks about Rust ownership, lifetimes, or borrow checker issues in async code -- reach for rust-ownership-patterns (note: lifetime issues in async closures are ownership topics, not async patterns)
The user asks about general Rust performance optimization, SIMD, memory layout, or allocation strategies -- use rust-performance
The user asks about Result, ? operator, thiserror/anyhow crate selection, or error propagation strategy -- use rust-error-handling
The user needs synchronous Rust concurrency (Rayon for CPU parallelism, std::thread, Arc<Mutex<T>>) without any async involvement
The user is writing embedded Rust (no_std) where standard async runtimes are unavailable -- specialized embedded async (embassy) is a separate concern
Process
1. Classify the Async Use Case
Before writing any code, determine which async problem category applies:
I/O concurrency -- multiple network calls, file reads, or database queries that can overlap. The dominant case. Answer: tokio::spawn or join! depending on whether tasks are independent or coordinated.
CPU-bound work inside async -- image encoding, cryptography, serialization of large payloads. Answer: tokio::task::spawn_blocking or a dedicated thread pool. Never run CPU work >~1ms directly on the async executor.
Structured fan-out -- launch N tasks, collect all results or short-circuit on first error. Answer: JoinSet for ownership-clean collection, FuturesUnordered when you want stream-style polling.
Long-running background tasks -- health check loops, metric flushers, connection pool maintainers. Answer: tokio::spawn at application startup with AbortHandle stored for clean shutdown.
Async streams/producers -- generating values asynchronously over time. Answer: tokio_stream, async-stream crate, or manual Stream impl.
Single-threaded async (e.g., browser WASM, LocalSet) -- futures that contain Rc<T> or aren't Send. Answer: tokio::task::LocalSet + spawn_local.
Identify which category applies before touching runtime selection. Multiple categories can coexist in one application.