rust-async

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::Mutex twice (use parking_lot::ReentrantMutex if needed)
  • Scope locks tightly: Drop guards as soon as possible
  • Atomics for primitives: Use AtomicBool/AtomicUsize instead of Mutex<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 points

Channel 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;
                }
            }
        }
    }
}
Weekly Installs
3
GitHub Stars
3
First Seen
Feb 9, 2026
Installed on
opencode3
gemini-cli3
claude-code3
github-copilot3
codex3
kimi-cli3