rust-concurrency
Skill1 starsChanged 6 months agoMITView on GitHub
Rust concurrency — threads, channels, shared state, Send/Sync. Use this skill when: - User asks about std::thread, thread::spawn, or thread::scope - User encounters E0277 "not Send" or "not Sync" errors - User uses Arc, Mutex, RwLock, or atomic types - User works with std::sync::mpsc or crossbeam channels - User implements a thread pool or work-stealing pattern - User asks about rayon for data parallelism - User encounters deadlocks or race conditions - User asks about lock-free data structures or atomics - User asks "when should I use threads vs async?" Trigger for OS thread concurrency questions (use rust-async for Tokio/async).
7 other projects use a version of this.
Adopted by
Most recently taken up by
- inference-gateway/skillstheir own version, first seen Nov 5, 2026
- owenlamont/skillstheir own version, first seen Oct 20, 2026
- full-stack-skills/rust-skillstheir own version, first seen Oct 15, 2026
- Lu1sDV/skillsmdtheir own version, first seen Oct 13, 2026
- Dankosik/rust-cli-skillstheir own version, first seen Sep 8, 2026
- botirkhaltaev/botir-skillstheir own version, first seen Jul 24, 2026
Used with
Projects with it also use rust-macros.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
dawidpereiraSkillrust-macros
Rust macros — macro_rules!, proc macros, derive macros, attribute macros, fragment specifiers, repetition, hygiene, cargo expand, when to use macros vs generics. Use when writing or debugging declarative or procedural macros, choosing between macros and other abstractions, understanding expansion errors, or generating repetitive code.
Also in 3 projects that use this
Other versions7 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 7
- huiali/rust-skills★ 47same name
- full-stack-skills/rust-skills★ 8same name
- botirkhaltaev/botir-skills★ 4same name
- inference-gateway/skills★ 3same name
- Lu1sDV/skillsmd★ 2same name
- owenlamont/skills★ 1same name
- Dankosik/rust-cli-skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository24 other files it gives its agents.
- rust-api-designSkill
- rust-asyncSkill
- rust-axumSkill
- rust-cliSkill
- rust-cloud-nativeSkill
- rust-databaseSkill
- rust-dioxusSkill
- rust-ecosystemSkill
- rust-embeddedSkill
- rust-error-handlingSkill
- rust-fintechSkill
- rust-masterySkill
- rust-memory-optimizationSkill
- rust-mlSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-ownershipSkill
- rust-design-patternsSkill
- rust-performanceSkill
- rust-securitySkill
- rust-testingSkill
- rust-type-systemSkill
- rust-unsafeSkill
- rust-wasmSkill
The file
We split this skill into the rules it gives an agent
5 rules. 0 are common, 5 are distinctive, and 0 are very specific to this project.
0
Common
Most of the 6 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 387
Prefer message passing or ownership transfer before shared mutable state.
- Distinctive: line 388
Use bounded channels when producers can outpace consumers.
- Distinctive: line 389
Keep lock scopes small and never hold locks across blocking operations.
- Distinctive: line 390
Document lock ordering when multiple locks are unavoidable.
- Distinctive: line 391
Use atomics only with a stated ordering invariant.
“Common” is measured: kept by at least 6 in 10 of the 6 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Quick Navigation
- references/sync_primitives.md
- references/lockless.md
Rust Concurrency
Rust's ownership model prevents data races at compile time. If it compiles, it's data-race-free.
The Core Guarantee
Send: A type can be transferred to another thread
Sync: A type can be shared (via &T) across threads
// Compiler-enforced rules:
T: Send → you can move T to another thread
T: Sync → you can share &T across threads
Arc<T>: Send iff T: Send + Sync
Mutex<T>: Send iff T: SendThreads vs Async
std::thread | tokio::spawn | |
| Best for | CPU-bound, blocking | I/O-bound, many connections |
| Cost | OS thread (~1MB stack) | Task (~few KB) |
| Blocking | Natural | Must use spawn_blocking |
| Parallelism | True parallel | Concurrent (1+ threads) |
Use threads when: crunching numbers, image processing, parsing large files. Use async when: waiting on network, database, disk I/O.
Basic Threads
use std::thread;
use std::time::Duration;
// Spawn a thread
let handle = thread::spawn(|| {
println!("Hello from thread!");
42
});
let result = handle.join().unwrap(); // Wait and get return value
println!("Thread returned: {result}");
// Move data into thread
let data = vec![1, 2, 3];
let handle = thread::spawn(move || { // `move` captures data by value
println!("Got: {:?}", data);
data.len()
});Thread Scope (Borrow in Threads)
use std::thread;
let data = vec![1, 2, 3, 4, 5, 6, 7, 8];
// thread::scope lets you borrow data — threads can't outlive the scope
thread::scope(|s| {
let chunk1 = &data[..4];
let chunk2 = &data[4..];
let t1 = s.spawn(|| chunk1.iter().sum::<i32>());
let t2 = s.spawn(|| chunk2.iter().sum::<i32>());
let sum = t1.join().unwrap() + t2.join().unwrap();
println!("Sum: {sum}");
}); // Scope ensures all threads finish before data is droppedShared State
Arc<Mutex<T>>
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0u64));
let handles: Vec<_> = (0..10).map(|_| {
let counter = Arc::clone(&counter); // Cheap: increments reference count
thread::spawn(move || {
let mut guard = counter.lock().unwrap();
*guard += 1;
// Guard dropped here: lock released
})
}).collect();
for h in handles { h.join().unwrap(); }
println!("Final: {}", *counter.lock().unwrap()); // 10Pitfalls:
// BAD: Holding lock across blocking operations
let guard = mutex.lock().unwrap();
some_slow_io_call(); // Other threads blocked the whole time!
// GOOD: Clone data, release lock early
let data = mutex.lock().unwrap().clone();
drop(data); // Explicitly release
some_slow_io_call();Arc<RwLock<T>> — Many Readers, One Writer
use std::sync::{Arc, RwLock};
let config = Arc::new(RwLock::new(Config::default()));
// Many readers can hold read lock simultaneously
let reader1 = {
let c = Arc::clone(&config);
thread::spawn(move || {
let guard = c.read().unwrap();
println!("timeout: {}", guard.timeout);
})
};
// Writer blocks until all readers release
let writer = {
let c = Arc::clone(&config);
thread::spawn(move || {
let mut guard = c.write().unwrap();
guard.timeout = 60;
})
};RwLock risk: writer starvation. If readers are constant, writers wait forever.
For read-heavy workloads with infrequent writes, consider arc-swap crate.
Channels (Message Passing)
std::sync::mpsc
use std::sync::mpsc;
use std::thread;
// Unbounded channel
let (tx, rx) = mpsc::channel::<String>();
// Multiple senders
for i in 0..5 {
let tx = tx.clone();
thread::spawn(move || {
tx.send(format!("Message {i}")).unwrap();
});
}
drop(tx); // Close original sender so rx knows when all senders are done
// Receive all messages
for msg in rx {
println!("Got: {msg}");
}
// Bounded (backpressure)
let (tx, rx) = mpsc::sync_channel::<u32>(16);Crossbeam Channels (Better)
use crossbeam::channel::{bounded, unbounded, select};
// Multiple producers AND consumers
let (tx, rx) = bounded::<Work>(100);
// Producer threads
for i in 0..4 {
let tx = tx.clone();
thread::spawn(move || {
loop {
tx.send(Work::new(i)).unwrap();
}
});
}
// Consumer threads
for _ in 0..4 {
let rx = rx.clone(); // crossbeam channels are multi-consumer
thread::spawn(move || {
for work in &rx {
process(work);
}
});
}
// select! — first-ready wins
select! {
recv(rx1) -> msg => handle_msg(msg),
recv(rx2) -> msg => handle_msg(msg),
recv(timeout_rx) -> _ => return Err(Timeout),
}Rayon: Data Parallelism
use rayon::prelude::*;
let data: Vec<u64> = (0..1_000_000).collect();
// Sequential
let sum: u64 = data.iter().map(|&x| x * x).sum();
// Parallel — same API, just .par_iter()
let sum: u64 = data.par_iter().map(|&x| x * x).sum();
// Parallel sort
let mut v: Vec<i32> = (0..1000).rev().collect();
v.par_sort();
// Parallel map + filter + collect
let results: Vec<String> = names
.par_iter()
.filter(|n| n.starts_with('A'))
.map(|n| format!("Hello, {n}!"))
.collect();
// Spawn two parallel tasks
let (sum, product) = rayon::join(
|| data.par_iter().sum::<u64>(),
|| data.par_iter().product::<u64>(),
);Atomic Operations
For simple counters and flags without mutexes:
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
let counter = Arc::new(AtomicUsize::new(0));
let shutdown = Arc::new(AtomicBool::new(false));
// In threads:
counter.fetch_add(1, Ordering::Relaxed); // Increment
let count = counter.load(Ordering::Acquire); // Read
shutdown.store(true, Ordering::Release); // Signal
// Ordering cheatsheet:
// Relaxed: no synchronization, just atomicity (counters OK)
// Acquire: synchronize with Release store (pair with Release)
// Release: synchronize with Acquire load (pair with Acquire)
// SeqCst: total order across all threads (use when Acquire/Release aren't enough)Thread Pool
// Use rayon for data parallelism
// Use tokio for async I/O
// Custom pool with crossbeam:
struct ThreadPool {
workers: Vec<thread::JoinHandle<()>>,
tx: crossbeam::channel::Sender<Box<dyn FnOnce() + Send>>,
}
impl ThreadPool {
fn new(n: usize) -> Self {
let (tx, rx) = crossbeam::channel::bounded::<Box<dyn FnOnce() + Send>>(64);
let rx = std::sync::Arc::new(rx);
let workers = (0..n).map(|_| {
let rx = Arc::clone(&rx);
thread::spawn(move || {
for task in rx.iter() {
task();
}
})
}).collect();
Self { workers, tx }
}
fn execute<F: FnOnce() + Send + 'static>(&self, f: F) {
self.tx.send(Box::new(f)).unwrap();
}
}Debugging Concurrency Issues
Detecting Deadlocks
// Classic deadlock: A locks mutex1 then mutex2, B locks mutex2 then mutex1
// Fix: Always acquire locks in the same order
// Or: Use a single mutex for both, or restructure data
// Detect at runtime (parking_lot feature):
use parking_lot::deadlock;
thread::spawn(|| {
loop {
thread::sleep(Duration::from_secs(10));
let deadlocks = deadlock::check_deadlock();
if !deadlocks.is_empty() {
eprintln!("{} deadlocks detected", deadlocks.len());
}
}
});Loom: Testing Concurrency
// Use loom for model-checking concurrent code
#[cfg(loom)]
use loom::sync::atomic::{AtomicUsize, Ordering};
#[cfg(loom)]
#[test]
fn test_concurrent_increment() {
loom::model(|| {
let counter = Arc::new(AtomicUsize::new(0));
let c1 = Arc::clone(&counter);
let c2 = Arc::clone(&counter);
let t1 = loom::thread::spawn(move || c1.fetch_add(1, Ordering::SeqCst));
let t2 = loom::thread::spawn(move || c2.fetch_add(1, Ordering::SeqCst));
t1.join().unwrap();
t2.join().unwrap();
assert_eq!(counter.load(Ordering::SeqCst), 2);
});
}Common Patterns
// Producer-consumer with backpressure
let (tx, rx) = crossbeam::channel::bounded(1000);
// Fan-out: one sender, multiple receivers
let (tx, rx) = crossbeam::channel::unbounded();
let receivers: Vec<_> = (0..4).map(|_| rx.clone()).collect();
// Fan-in: multiple senders, one receiver
let (tx, rx) = crossbeam::channel::unbounded();
let senders: Vec<_> = (0..4).map(|_| tx.clone()).collect();
drop(tx); // Close original
// One-shot: signal completion
let (done_tx, done_rx) = crossbeam::channel::bounded::<()>(1);
thread::spawn(move || {
do_work();
done_tx.send(()).unwrap();
});
done_rx.recv().unwrap(); // Wait for completionReview Checklist
- Prefer message passing or ownership transfer before shared mutable state.
- Use bounded channels when producers can outpace consumers.
- Keep lock scopes small and never hold locks across blocking operations.
- Document lock ordering when multiple locks are unavoidable.
- Use atomics only with a stated ordering invariant.
- Model-check custom synchronization with
loombefore trusting tests.
References
- Rust Book: Fearless Concurrency
- crossbeam
- rayon
- parking_lot — faster mutex/rwlock
- loom — concurrency model checker
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

