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"]