agentsclimarketplace

Rust async

Skill dawidpereira/rust-skills/skills/rust-async

Async Rust and concurrency with Tokio. Use when writing async code, choosing channel types (mpsc/broadcast/watch/oneshot), dealing with Send/Sync bounds, spawn_blocking, JoinSet, CancellationToken, or fixing issues with locks held across .await points. Also use for tokio::select!, graceful shutdown, and structured concurrency patterns.From its SKILL.md

Install
npx -y skills add dawidpereira/rust-skills --skill rust-async

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

7.3 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it

Async & Concurrency

Core Question

Is this I/O-bound (async) or CPU-bound (threads/spawn_blocking)?

Async is for waiting on external things (network, disk, timers). CPU-heavy work blocks the runtime — move it to spawn_blocking or a dedicated thread pool.


Error → Design Question

SymptomDon't Just SayAsk Instead
Future is not Send"Add Send bound"Why does data cross a thread boundary? Can you restructure?
Deadlock with Mutex"Use tokio::sync::Mutex"Should you hold a lock across await at all?
Task hangs forever"Add timeout"Is there a cancellation path?
Channel fills up"Make it unbounded"What's the backpressure strategy?

Quick Decisions

SituationReach ForWhy
Run independent futures concurrentlytokio::join!Runs all, returns all results
Run fallible futures, fail fasttokio::try_join!Returns first error, drops rest
Race futures, handle first completiontokio::select!Cancel losers automatically
Dynamic number of spawned tasksJoinSetAdd/remove tasks, collect results
CPU-intensive work in async contextspawn_blockingMoves to blocking thread pool
File I/O in async codetokio::fsNon-blocking file operations
Graceful shutdownCancellationTokenHierarchical cancellation
One producer, one consumer, one messageoneshotRequest-response pattern
Work queue (multiple producers)mpsc (bounded)Backpressure built in
All subscribers get all messagesbroadcastPub/sub pattern
Share latest value, skip intermediatewatchConfig updates, state sharing
Shared read-only data across tasksArc<T>Clone Arc, not the data
Shared mutable state across tasksArc<Mutex<T>>Or Arc<RwLock<T>> if reads dominate
Stream of async valuestokio_stream + StreamExtAsync equivalent of Iterator
Paginated API consumptionstream::unfoldLazy, backpressure-aware page fetching
CPU-bound data parallelismrayon::par_iterAutomatic work-stealing across cores
CPU-bound work in async contextspawn_blocking + rayonKeep the async runtime unblocked
Scoped parallel work (no Arc)std::thread::scopeBorrows stack data safely across threads
Atomic flag or counterAtomicBool / AtomicUsizeLock-free, single-word synchronization
async fn in trait definitionnative async fn in traitsNo #[async_trait] needed since Rust 1.75

Channel Selection

ChannelPatternCapacityReceivers
oneshotRequest → Response1 message1
mpscWork queueBounded (set capacity)1
broadcastPub/sub (all get all)Bounded (ring buffer)N (all messages)
watchLatest value1 (latest only)N (skip to newest)

Always use bounded channels unless you have a specific reason not to. Unbounded channels grow without limit when producer outpaces consumer.

Buffer sizing: start with num_producers * 2 or expected burst size. Monitor with capacity() and len().


The Lock-Across-Await Problem

Never hold a std::sync::Mutex guard across an .await:

// Bad: guard held across await — can deadlock
let mut guard = data.lock().unwrap();
*guard = fetch().await;

// Good: extract, await, then lock again
let current = data.lock().unwrap().clone();
let new_data = process(current).await;
*data.lock().unwrap() = new_data;

tokio::sync::Mutex is await-safe but has higher overhead. Prefer restructuring to avoid holding locks across await entirely.


Usage Scenarios

Scenario 1: "I need to make 5 HTTP requests and combine the results" → Use tokio::try_join! for a fixed number, or JoinSet for a dynamic number. Don't await them sequentially — that's 5x slower.

Scenario 2: "My async task needs to do JSON parsing on large payloads" → JSON parsing is CPU-bound. Use spawn_blocking(move || serde_json::from_str(&data)) to avoid blocking the runtime.

Scenario 3: "I need to shut down gracefully when Ctrl+C is pressed" → Create a CancellationToken, pass child tokens to tasks, and use tokio::select! to race work against token.cancelled(). On signal, cancel the root token.


Reference Files

FileRead When
references/tokio-patterns.mdRuntime setup, spawn_blocking, join/select patterns, JoinSet, cancellation
references/channels.mdChoosing and using mpsc/broadcast/watch/oneshot, backpressure, message patterns
references/safety.mdLock safety across await, Send/Sync issues, clone-before-await patterns
references/streams.mdStream trait, StreamExt, Pin, async fn in traits, paginated/WebSocket patterns
references/threads-and-parallelism.mdstd::thread, rayon, crossbeam, atomics, async-vs-threads decision

Cross-References

WhenCheck
Smart pointers for shared state (Arc, Mutex)rust-ownership → Quick Decisions
Error handling in async (try_join, ?)rust-errors → Quick Decisions
Async trait design and Send boundsrust-types → Quick Decisions
Tokio runtime profile settingsrust-perf → Quick Decisions
Tracing spans in async code, .instrument()rust-tracing → Quick Decisions
Rayon, parallel iteratorsrust-perf → Quick Decisions

What ships with it: 5 files

29.0 KB alongside SKILL.md

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.