Overview
"Fearless concurrency" is a precise claim: the aliasing rules from ownership & borrowing, lifted to thread boundaries by two marker traits, make data races a compile error — the bug class Java's JMM exists to reason about and Go's race detector exists to catch at runtime simply doesn't ship. The claim is exactly that wide and no wider: deadlocks, livelocks, starvation and logic races (check-then-act at the wrong granularity) remain yours. This note covers the mechanism and the toolbox — threads, channels, locks, atomics — and where the fear legitimately resumes.
Key points
SendandSyncare inferred theorems:Send= the type may move to another thread;Sync=&Tmay be shared across threads (T: Sync⇔&T: Send). The compiler derives them structurally — a type is Send/Sync iff its fields are — and everyspawnchecks them. Non-Sendtypes tell you why:Rc(non-atomic counts),RefCell's guards (single-thread checks), raw pointers (no promises).- The composition rules are the design language:
Mutex<T>: Synceven forT: !Sync— the lock adds thread-safety;Arc<T>requiresT: Send + Syncto be useful across threads. "Rc<RefCell<T>>single-threaded ⇄Arc<Mutex<T>>threaded" is the canonical rung-for-rung upgrade, enforced, not conventional. thread::spawndemands'static: the closure must own its captures (move semantics + theT: 'staticbound) because the thread may outlive the caller. Scoped threads (thread::scope, stable 1.63) restore borrowing — the scope guarantees joining before borrowed locals die; fork-join over local data without a singleArc.- Channels for transfer of ownership: std's
mpscworks; crossbeam-channel (or flume) is the production default — faster, MPMC,select!. Sending a value moves it — "share memory by communicating" with the no-use-after-send rule compiler-enforced, where Go documents the convention. Arc<Mutex<T>>and poisoning: std'sMutexpoisons on panic-while-held (.lock().unwrap()propagates); parking_lot offers smaller, faster, non-poisoning locks and is ubiquitous.RwLockfor read-heavy; keep critical sections tiny; the lock owns the data (Mutex<T>, notMutexbeside data) — the structural difference from Java'ssynchronized, where the lock and the data it guards are related only by discipline.- Atomics use the C++11 ordering model:
AtomicUsize& co. withRelaxed(counters),Acquire/Release(publish/consume handshakes — the happens-before edge, same concept as the JMM's),SeqCst(when in doubt, and for the rare global order). Raw atomics are the one corner where fearless needs a citation — Mara Bos's book is the modern reference, and loom model-checks lock-free code across interleavings. - rayon owns data parallelism:
.iter()→.par_iter()turns a pipeline parallel with work-stealing; join/scope for divide-and-conquer. The borrow rules make the "is this loop body independent?" question a compile-time fact, which is why the API can be that small. - The async twist:
tokio::spawnadds the sameSend + 'staticbounds (the multi-threaded runtime moves tasks between workers),Arc<Mutex<T>>remains the shared-state idiom (std/parking_lot for short sync sections;tokio::sync::Mutexonly across.awaitpoints), and cancellation-by-drop is the async-specific hazard — the runtime side lives in async runtimes. - What fearless does NOT mean: deadlocks compile fine (lock ordering is still
yours),
RefCellpanics andMutexcontention are runtime behaviour, and atomicity granularity (two lock acquisitions ≠ one transaction) is still a design problem. Rust deletes data races; it politely declines to delete concurrency.
Details
The toolbox, by need
| Need | Reach for | Notes |
|---|---|---|
| Fork-join over local data | thread::scope |
Borrows locals; joins at scope end |
| Long-lived workers + queue | crossbeam-channel / flume | MPMC, select!, bounded = backpressure |
| Shared mutable state | Arc<Mutex<T>> / Arc<RwLock<T>> (parking_lot) |
Lock owns the data; keep sections small |
| Shared counters/flags | AtomicUsize, AtomicBool (Relaxed/AcqRel) |
Orderings per the C++11 model |
| Embarrassing parallelism | rayon par_iter |
Work-stealing pool, zero setup |
| Lock-free structures | crossbeam (epoch GC), verified with loom | The deep end — measure first |
| Async tasks & I/O | Tokio — async runtimes | Same Send/Sync rules, plus cancellation |
The one-slide contrast
Java: one shared-memory model (the JMM), safety by discipline + java.util.concurrent,
data races are possible and defined. Go: goroutines + channels by culture, shared
memory by convention, data races are possible and detected (-race). Rust: both
styles available, data races are impossible outside unsafe — and the type system
is what tells you which tool a piece of data needs. The price tag: Send + 'static
bounds surfacing in APIs, and the up-front design work the
fights table describes, now with threads.
Examples
// Scoped threads — fork-join borrowing locals, no Arc:
let chunks: Vec<&[Record]> = data.chunks(data.len() / workers).collect();
let totals: Vec<u64> = std::thread::scope(|s| {
chunks.iter()
.map(|chunk| s.spawn(|| chunk.iter().map(|r| r.amount).sum()))
.collect::<Vec<_>>()
.into_iter()
.map(|h| h.join().unwrap())
.collect()
});
// The enforced upgrade: Rc<RefCell<_>> → Arc<Mutex<_>> to cross threads
let shared = Arc::new(Mutex::new(HashMap::new()));
let worker = Arc::clone(&shared);
std::thread::spawn(move || { worker.lock().unwrap().insert(k, v); });
// rayon — parallelism as an iterator adapter:
let sum: u64 = records.par_iter().map(|r| r.amount).sum();
Related
- Ownership, memory & concurrency — the model — parent map of the group.
- Ownership & borrowing — the aliasing rules doing the actual work here.
- Memory without GC —
Arc,Mutexand the interior-mutability ladder these tools sit on. - Lifetimes — the
T: 'staticbound on spawning, decoded. - Async runtimes — tasks instead of threads, same guarantees.
- The Java Memory Model — the happens-before vocabulary Rust's orderings share.
- Concurrency and Parallelism — the cross-language crossroads.
- The Rust Programming Language — ch. 16 is the canonical tour.
Citations
[1] The Book, ch. 16 — Fearless Concurrency [2] Rust Atomics and Locks — Mara Bos [3] The Rustonomicon — Concurrency