Arc<Mutex<T>>
Arc<Mutex<T>> is the standard composition of std::sync::Arc (thread-safe shared ownership) and std::sync::Mutex (thread-safe interior mutability) for sharing mutable heap-allocated state across multiple unconstrained threads (std::thread::spawn or multi-threaded async tasks).
Why Arc and Mutex Complement Each Other
Neither type can provide shared mutable state across 'static threads on its own:
Mutex<T>allows mutatingTthrough a shared reference&Mutex<T>, but passing&Mutex<T>intostd::thread::spawnfails unless theMutex<T>lives for'static.Arc<T>keeps a heap allocation alive across arbitrary'staticthreads via atomic reference counting, butArc<T>only yields a shared&Treference.- Combining them as
Arc<Mutex<T>>lets each thread own anArcclone that dereferences to&Mutex<T>, and calling.lock()on&Mutex<T>yields a mutableMutexGuard<'_, T>(&mut T)!
Thread 1 Stack Single Heap Allocation Thread 2 Stack
+--------------------+ +-----------------------------------+ +--------------------+
| arc_1: Arc<...> ---+-----> | ArcInner<Mutex<T>> | <-+--- arc_2: Arc<...> |
+--------------------+ | strong: AtomicUsize (2) | +--------------------+
| weak: AtomicUsize (1) |
| data: Mutex<T> |
| +-- lock state (AtomicU32) |
| +-- UnsafeCell<T> (Actual T) |
+-----------------------------------+
Notice that Arc::new(Mutex::new(value)) performs only one heap allocation (for ArcInner<Mutex<T>>); the Mutex<T> and T sit directly inline inside that single ArcInner block!
Idiomatic Encapsulation: Hide Arc<Mutex<Inner>> Inside a Domain Struct
Instead of exposing raw Arc<Mutex<State>> in function signatures across your codebase (which forces callers to manually .lock().unwrap() and risks callers holding guards too long), wrap Arc<Mutex<Inner>> inside a domain struct with #[derive(Clone)] and expose clean, short-critical-section methods:
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
use std::thread;
#[derive(Clone)]
pub struct WorkQueue {
// Private field: callers cannot accidentally hold a `MutexGuard` across I/O!
inner: Arc<Mutex<VecDeque<String>>>,
}
impl WorkQueue {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(VecDeque::new())),
}
}
pub fn push(&self, task: impl Into<String>) {
self.inner.lock().unwrap().push_back(task.into());
}
pub fn pop(&self) -> Option<String> {
self.inner.lock().unwrap().pop_front()
}
pub fn len(&self) -> usize {
self.inner.lock().unwrap().len()
}
}
fn main() {
let queue = WorkQueue::new();
// Clone the lightweight `WorkQueue` handle for producer threads:
let mut producers = Vec::new();
for id in 0..3 {
let q = queue.clone();
producers.push(thread::spawn(move || {
q.push(format!("task-from-{}", id));
}));
}
for p in producers {
p.join().unwrap();
}
println!("queued items: {}", queue.len());
while let Some(task) = queue.pop() {
println!("popped: {}", task);
}
}
queued items: 3
popped: task-from-0
popped: task-from-1
popped: task-from-2
Recovering Unique Ownership After Workers Finish (Arc::into_inner / Arc::try_unwrap)
Once all worker threads have joined and dropped their Arc clones (leaving strong_count == 1), calling Arc::into_inner(arc)?.into_inner().unwrap() dissolves both the Arc and the Mutex and returns the owned T by value without locking or cloning!