Arc vs Mutex in Rust: What They Do and When You Need Both
March 5, 2026 · 5 min read
Arc and Mutex solve two completely different problems — but you almost always need them together. Here's why.
One of the first things that confuses Rust newcomers coming from Go or TypeScript is why you need both Arc and Mutex to share data across threads. They solve different problems, and understanding each in isolation makes the combination obvious.
Arc: Shared Ownership Across Threads
Arc<T> stands for Atomically Reference Counted. It lets multiple threads hold a reference to the same heap-allocated value, with the value being dropped when the last reference is gone. Arc is about ownership — it answers 'who owns this and when should it be freed?'
use std::sync::Arc;
use std::thread;
let data = Arc::new(vec![1, 2, 3]);
let data_clone = Arc::clone(&data);
thread::spawn(move || {
println!("{:?}", data_clone); // can read, but cannot mutate
});Mutex: Exclusive Access for Mutation
Mutex<T> wraps a value and ensures only one thread can access it at a time. It answers 'who is allowed to mutate this right now?' Critically, Arc alone gives you shared read-only access. To mutate, you need Mutex.
use std::sync::{Arc, Mutex};
use std::thread;
let counter = Arc::new(Mutex::new(0));
let handles: Vec<_> = (0..10).map(|_| {
let c = Arc::clone(&counter);
thread::spawn(move || {
let mut num = c.lock().unwrap();
*num += 1;
})
}).collect();
for h in handles { h.join().unwrap(); }
println!("{}", *counter.lock().unwrap()); // 10Why You Almost Always Use Arc<Mutex<T>>
Arc without Mutex: multiple threads can read shared data, but you can't mutate it safely. Mutex without Arc: only one thread can own the Mutex at all — useless for sharing. Arc<Mutex<T>> = shared ownership + controlled mutation. That's the combination you need for almost any shared mutable state across threads.
Alternatives to Consider
- ▸RwLock<T>: multiple concurrent readers OR one writer — better than Mutex for read-heavy workloads
- ▸Atomic types (AtomicUsize, AtomicBool): lock-free primitives for simple counters and flags
- ▸Channels (mpsc): prefer message passing over shared state when possible — avoids locks entirely
- ▸DashMap: concurrent HashMap that handles its own locking internally