Synchronization Primitives

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 mutating T through a shared reference &Mutex<T>, but passing &Mutex<T> into std::thread::spawn fails unless the Mutex<T> lives for 'static.
  • Arc<T> keeps a heap allocation alive across arbitrary 'static threads via atomic reference counting, but Arc<T> only yields a shared &T reference.
  • Combining them as Arc<Mutex<T>> lets each thread own an Arc clone that dereferences to &Mutex<T>, and calling .lock() on &Mutex<T> yields a mutable MutexGuard<'_, 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!