Synchronization Primitives

Mutex<T>

std::sync::Mutex<T> is a mutual-exclusion synchronization primitive that protects shared data T by ensuring at most one thread can read or mutate T at any given instant. In Rust, the mutex owns the protected value T directly inside its internal UnsafeCell<T>, so callers cannot access T without first locking the mutex and receiving a MutexGuard<'_, T>.

Internal Architecture of Mutex<T>

On modern operating systems (Linux, macOS, Windows), std::sync::Mutex<T> is implemented as a compact futex-based lock (only 8 bytes of lock-word overhead plus size_of::<T>() on Linux/Windows/macOS in recent Rust releases) rather than a heap-allocated pthread_mutex_t:

Mutex<T> Memory Layout:
+-----------------------------------+-----------------------------------+
| state: AtomicU32 (Unlocked /      | poison: AtomicBool (Has a holder  |
|   Locked / Contended Waiters)     |   thread panicked while locked?)  |
+-----------------------------------+-----------------------------------+
| data: UnsafeCell<T> (Inline storage of the protected value T)         |
+-----------------------------------------------------------------------+
  • Uncontended fast path: When no other thread holds the lock, .lock() performs a single atomic compare-and-exchange instruction in user space without making an OS system call.
  • Contended slow path: If another thread holds the lock, .lock() spins very briefly and then parks the calling thread in the OS kernel futex queue until the holder unlocks it.

Complete Mutex<T> API Reference

Method Signature Requires OS Lock? Description
Mutex::new(val) const fn new(t: T) -> Mutex<T> No Constructs an unlocked Mutex<T> (usable directly in static)
.lock(&self) fn lock(&self) -> LockResult<MutexGuard<'_, T>> Yes Blocks until lock is acquired
.try_lock(&self) fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> Yes (non-blocking) Returns Ok(guard) immediately if free, or Err(TryLockError::WouldBlock)
.get_mut(&mut self) fn get_mut(&mut self) -> LockResult<&mut T> No Returns &mut T directly because &mut self proves zero concurrent threads
.into_inner(self) fn into_inner(self) -> LockResult<T> No Consumes the Mutex<T> by value and returns the inner T
.is_poisoned(&self) fn is_poisoned(&self) -> bool No Checks whether a thread panicked while holding this lock
.clear_poison(&self) fn clear_poison(&self) No Resets the poisoned flag back to false

Static Global Mutex<T> Without LazyLock

Because Mutex::new is a const fn, any const-constructible value (Vec::new(), String::new(), 0_u64, None) can be placed inside a static Mutex<T> directly:

use std::sync::Mutex;
use std::thread;

static EVENT_QUEUE: Mutex<Vec<String>> = Mutex::new(Vec::new());

fn enqueue(event: &str) {
    EVENT_QUEUE.lock().unwrap().push(event.to_string());
}

fn main() {
    thread::scope(|s| {
        s.spawn(|| enqueue("worker-a:ready"));
        s.spawn(|| enqueue("worker-b:ready"));
    });

    let mut events = EVENT_QUEUE.lock().unwrap().clone();
    events.sort();
    println!("queued events: {:?}", events);
}
queued events: ["worker-a:ready", "worker-b:ready"]