Use when writing async or concurrent Rust code with tokio, async-std, futures, channels, mutexes, threads, spawn, or runtime. Covers async patterns, Pin, poll, Stream, timeout, graceful shutdown, task management, blocking in async, mutex across await, select, JoinSet, CancellationToken, sync primitives, broadcast, mpsc, oneshot, watch channels, deadlock prevention, and thread safety.
Install
npx skillscat add peixotorms/odinlayer-skills/rust-async Install via the SkillsCat registry.
SKILL.md
Concurrency & Async
Thread Safety
| Error | Cause | Fix |
|---|---|---|
Rc<T> cannot be sent between threads |
Rc is not Send |
Use Arc |
RefCell<T> is not Sync |
Not thread-safe | Use Mutex or RwLock |
Cannot mutate through Arc |
No interior mutability | Arc<Mutex<T>> or Arc<AtomicT> |
Deadlock Prevention
- Lock ordering: Always acquire multiple locks in the same order across all threads
- Self-deadlock: Never lock the same
std::Mutextwice (useparking_lot::ReentrantMutexif needed) - Scope locks tightly: Drop guards as soon as possible
- Atomics for primitives: Use
AtomicBool/AtomicUsizeinstead ofMutex<bool> - Choose memory order carefully:
Relaxed/Acquire/Release/SeqCst
Async Patterns
| Pattern | When to Use |
|---|---|
tokio::spawn |
Fire-and-forget concurrent tasks |
tokio::select! |
Race multiple futures, cancel losers |
mpsc channel |
Fan-in: many producers, one consumer |
oneshot channel |
Single request-response |
broadcast channel |
One producer, many consumers |
watch channel |
Latest-value state sharing |
JoinSet |
Manage group of spawned tasks |
CancellationToken |
Graceful shutdown signaling |
Critical Async Mistakes
Mutex Guard Across Await
// BAD: std::Mutex guard held across .await — blocks executor
async fn bad() {
let guard = mutex.lock().unwrap();
some_async_op().await; // guard held across await!
println!("{}", *guard);
}
// GOOD: scope the lock, copy what you need
async fn good() {
let value = {
let guard = mutex.lock().unwrap();
*guard // copy value
}; // guard dropped here
some_async_op().await;
println!("{}", value);
}Blocking in Async Context
// BAD: blocking call in async context
async fn bad() {
std::thread::sleep(Duration::from_secs(1)); // blocks executor!
std::fs::read_to_string("file.txt").unwrap(); // blocks!
}
// GOOD: use async equivalents
async fn good() {
tokio::time::sleep(Duration::from_secs(1)).await;
tokio::fs::read_to_string("file.txt").await.unwrap();
}
// For CPU work: spawn_blocking
let result = tokio::task::spawn_blocking(|| heavy_computation()).await.unwrap();Forgetting to Poll Futures
// BAD: future not polled — does nothing!
fn process() {
fetch_data(); // returns Future that's immediately dropped
}
// GOOD: await it
async fn process() {
let data = fetch_data().await;
}Key Rule
// Sync for CPU-bound work, async for I/O-bound work
// Don't hold locks across await pointsChannel Disconnection
// Iterate until all senders drop
for msg in rx {
handle(msg);
}
// Explicit matching for non-blocking
match rx.try_recv() {
Ok(msg) => handle(msg),
Err(TryRecvError::Empty) => continue,
Err(TryRecvError::Disconnected) => break,
}Thread Panic Handling
let handle = std::thread::spawn(|| risky_operation());
match handle.join() {
Ok(result) => use_result(result),
Err(e) => eprintln!("Thread panicked: {:?}", e),
}Graceful Shutdown Pattern
use tokio_util::sync::CancellationToken;
async fn run(token: CancellationToken) {
loop {
tokio::select! {
_ = token.cancelled() => {
tracing::info!("shutting down");
break;
}
msg = rx.recv() => {
if let Some(msg) = msg {
handle(msg).await;
}
}
}
}
}