std.mutex
std.mutex provides Mutex<T>, the mutual-exclusion escape hatch for data that is not lock-free-atomic-eligible: it owns its T and hands out exclusive access under a lock. It is the type the Atomic<T> "not lock-free" diagnostic points at. Access is either scoped — run a body with the lock held, released automatically on exit — or through an RAII guard whose drop unlocks. For lock-free primitives use std.atomic; for the concurrency model see Concurrency And Parallelism.
Quick Example
import std.mutex;
fn main() {
var m = mutex.new<i32>(0);
// Scoped access: the lock is held for the body, released on return.
m.with((v) => { v = 42; });
m.read((v) => { assert v == 42; });
// RAII guard: lock() returns a guard; get() borrows the value; drop unlocks.
{
var g = m.lock();
g.get() = *g.get() + 1;
}
m.read((v) => { assert v == 43; });
}Contract
mutex.new<T>(value)mints an^Mutex<T>owningvalue. The lock object is heap-boxed behind an opaque handle, so aMutex<T>value may move freely before it is first locked; while a guard is live the borrow checker forbids moving it.Mutex<T>is move-only. Because acquiring the lock mutates through&self, the binding must bevar(see Sharing for the cross-thread story).- There is no poisoning. A panic aborts the process, so there is nothing to observe a poisoned state; and an error returned with
throwsfrom inside a scoped body still runs the release (scoped access unlocks viadefer, the guard via drop), so the lock is released and the data stays consistent on the error path.lockis infallible;try_withreports contention. - The mutex is not reentrant: locking it again on the same thread while it is already held deadlocks. The borrow checker catches the common case (a second
lockwhile the first guard is live is a borrow conflict).
Scoped Access
with, read, and try_with run a caller-supplied body with the lock held and release it when the body returns — the simplest and hardest-to-misuse form.
with(body)runsbodywith a mutable borrow of the inner value.read(body)runsbodywith a shared bare borrow.try_with(body)acquires the lock without blocking: it returnstrueand runsbodyif the lock was free, orfalseif it was contended.
import std.mutex;
fn main() {
var m = mutex.new<i32>(10);
let ran = m.try_with((v) => { v = *v + 5; });
assert ran;
m.read((v) => { assert v == 15; });
}The RAII Guard
lock() acquires the lock and returns a MutexGuard<T> that holds it. The guard borrows the mutex for a region tied to the enclosing scope, so:
- a second
lock()while the guard is live is rejected (you cannot alias the locked mutex); - the guard cannot outlive the mutex;
- the lock is released when the guard is dropped — at the end of its scope.
get() returns a mutable borrow of the guarded value, tied to the guard's region; mutating through it requires the guard binding to be var. Scope the guard tightly (an inner { ... } block) to bound how long the lock is held.
Sharing
Mutex<T> is the mutual-exclusion primitive together with its memory-ordering guarantee: acquiring the lock synchronizes-with the previous release, so data written under the lock is visible to the next holder. What it does not yet provide is a cross-thread sharing mechanism. Mutex<T> carries a drop, and a type with a user drop is currently rejected at a worker boundary, so a Mutex can be neither shared by borrow nor moved into a worker in this release. Multi-owner sharing (the Arc<Mutex<T>> pattern) is future work. Today the type is used for exclusion and interior mutability within a thread; for communication between tasks, use a channel.
Ownership, Allocation, And Errors
new heap-allocates the OS lock object once; Mutex<T> owns and drops its T through normal field cleanup, and destroying the lock is handled by the mutex's own drop. Scoped access and lock/get do not allocate. On single-threaded SDK targets without threads the lock degrades to a no-op (there is no contention to guard).
Related Modules
Use std.atomic for lock-free primitives, and std.channel for message passing instead of shared mutable state. See Concurrency And Parallelism for the runtime model and Standard Library Inventory for the full exported surface.
API Reference
Types
| Type | Kind | Description |
|---|---|---|
Mutex<T> | struct | Mutual-exclusion cell owning a T. Move-only. |
MutexGuard<T> | struct | RAII lock guard; get() borrows the value, drop unlocks. |
Functions
| Function | Signature | Returns | Description |
|---|---|---|---|
new | new<T>(value: ^T) -> ^Mutex<T> | ^Mutex<T> | Construct a mutex owning value. |
Mutex Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
with | with(&self, body: (&T) -> void) | — | Run body with a mutable borrow of the value under the lock. |
read | read(&self, body: (T) -> void) | — | Run body with a shared borrow of the value under the lock. |
try_with | try_with(&self, body: (&T) -> void) -> bool | bool | Non-blocking with; true if the lock was acquired, false if contended. |
lock | lock(&self) -> ^MutexGuard<T> | ^MutexGuard<T> | Acquire the lock and return a guard that releases it on drop. |
Guard Methods
| Method | Signature | Returns | Description |
|---|---|---|---|
get | get(self) -> &T | &T | Mutable borrow of the guarded value, tied to the guard's region. |
(Region and closure-region annotations are elided from the signatures above for readability; the guard's borrow is tied to the enclosing scope.)
Status
std.mutex is the current public mutual-exclusion API: Mutex<T> with scoped (with/read/try_with) and RAII-guard (lock/get) access over a pthread lock — scoped bodies unlock via defer, the guard via drop — with no poisoning. Shared-ownership across many threads (Arc<Mutex<T>>) and auto-deref guard access (g.field without get()) are not part of this release. For the full exported surface, see Standard Library Inventory.