rgoussu@goussu: ~/library/rust/ownership-and-concurrency
~/library/rust/ownership-and-concurrency cat fearless-concurrency.md

Fearless concurrency — Send, Sync, threads, channels & atomics

# How ownership crosses thread boundaries — the Send/Sync auto traits, spawn vs scoped threads, channels, Arc<Mutex<T>> and poisoning, atomics and memory orderings, rayon, and what fearless does not mean.

Conceptsaved 2026-08-09 #rust#concurrency#send-sync#atomics#threads#channels

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

  • Send and Sync are inferred theorems: Send = the type may move to another thread; Sync = &T may be shared across threads (T: Sync&T: Send). The compiler derives them structurally — a type is Send/Sync iff its fields are — and every spawn checks them. Non-Send types 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>: Sync even for T: !Sync — the lock adds thread-safety; Arc<T> requires T: Send + Sync to be useful across threads. "Rc<RefCell<T>> single-threaded ⇄ Arc<Mutex<T>> threaded" is the canonical rung-for-rung upgrade, enforced, not conventional.
  • thread::spawn demands 'static: the closure must own its captures (move semantics + the T: 'static bound) 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 single Arc.
  • Channels for transfer of ownership: std's mpsc works; 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's Mutex poisons on panic-while-held (.lock().unwrap() propagates); parking_lot offers smaller, faster, non-poisoning locks and is ubiquitous. RwLock for read-heavy; keep critical sections tiny; the lock owns the data (Mutex<T>, not Mutex beside data) — the structural difference from Java's synchronized, where the lock and the data it guards are related only by discipline.
  • Atomics use the C++11 ordering model: AtomicUsize & co. with Relaxed (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::spawn adds the same Send + 'static bounds (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::Mutex only across .await points), 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), RefCell panics and Mutex contention 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

Citations

[1] The Book, ch. 16 — Fearless Concurrency [2] Rust Atomics and Locks — Mara Bos [3] The Rustonomicon — Concurrency