tur/mutex
POSIX mutex (pthread_mutex_t) wrapped as ptr<void>.
Suitable for single-threaded and multi-threaded programs.
Since: Phase T19-C
Mutex
(defopaque Mutex :ptr<void> :linear)
mutex-new
(mutex-new :Mutex)
allocate and initialise a new POSIX mutex.
A Mutex handle to a heap-allocated pthread_mutex_t.
(let [m (mutex-new)] ...) ; => Mutex
Since: Phase T19-C
mutex-lock
(mutex-lock [^borrow m :Mutex] :nil)
acquire the mutex, blocking until it is available.
| m | mutex handle returned by mutex-new |
(mutex-lock m)
Since: Phase T19-C
mutex-unlock
(mutex-unlock [^borrow m :Mutex] :nil)
release the mutex.
| m | mutex handle returned by mutex-new |
(mutex-unlock m)
Since: Phase T19-C
mutex-try-lock
(mutex-try-lock [^borrow m :Mutex] :bool)
attempt to acquire the mutex without blocking.
| m | mutex handle returned by mutex-new |
true if the mutex was successfully acquired; false if it was already held.
(if (mutex-try-lock m) ...) ; => bool
Since: Phase T19-C
mutex-free
(mutex-free [m :Mutex] :nil)
destroy the mutex and release its memory.
| m | mutex handle returned by mutex-new |
(mutex-free m)
Since: Phase T19-C
with-lock
(with-lock [m & body])
run body with the mutex held, releasing it afterwards.
The scoped counterpart to the raw mutex-lock / mutex-unlock pair: the body's value is the form's value, and the unlock cannot be forgotten on the way out. Replaces the hand-written (mutex-lock m) ... (mutex-unlock m) sandwich, where the release drifts away from the acquire as the body grows and an early return skips it.
| m | a Mutex handle (borrowed; with-lock does not consume or free it) | |
| body | one or more forms; the last one's value is returned |
The value of the last body form.
(let [m (mutex-new)] (println (with-lock m (+ 1 41))) ; => 42 (mutex-free m))
Two caveats worth knowing:
m is evaluated twice -- once to lock, once to unlock -- so pass a VARIABLE, not a call. (with-lock (get-mutex) ...) would lock one mutex and unlock whatever the second call returns. A macro cannot bind it once instead, because Mutex is :linear: a let binding moves the handle, and the checker then rejects the scope for dropping it unconsumed (TUR-E0100).
The unlock is not panic-safe. If the body panics, the mutex stays locked. Guarding against that needs the unlock on an unwind path, which catch-unwind can express at the call site when it matters.
Since: 2026-08-20