Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

std.sync

Exports CancellationToken, the existing cooperative cancellation facility, and Mutex[T]. The imported and prelude CancellationToken types are identical; no wrapper, allocation, or new effect permission is introduced by importing. Mutex is not a prelude name: each use needs an import, through any item or module alias. Supported target: x86_64 Linux GNU.

CancellationToken() -> Result[CancellationToken, AllocError] creates an owned shared state in one fallible allocation.

MethodResultEffect
share()CancellationTokenExplicitly retain the same state
cancel()()Permanently request cancellation and wake token waiters
is_cancelled()boolObserve the request
wait()()Wait until cancellation is requested
wait_timeout_ms(timeout_ms: u64)boolObserve cancellation within the budget
cancel_on_exit()with guardCancel on every exit from the with block; see cancel on every exit

Methods borrow the handle, allocate nothing, and copy no captured data. Share and observation have constant bookkeeping; cancellation also wakes waiters. Lock contention and scheduling have no bounded latency. Tokens are safe to move or explicitly share into eligible workers; borrowed handles retain ordinary loans. Dropping the last handle releases the state but does not request stopping. Cancellation is idempotent and does not reset.

A token does not interrupt instructions, cancel a future, or wake a channel wait. Workers must return cooperatively, using bounded waits where needed. std.time supplies deadline arithmetic and timeout conversion.

Typed deadline waits

Prefer these waits when a caller supplies a total budget and cancellation. They borrow the handles, allocate nothing, and preserve the deadline across wakeups. deadline and cancel are the last two parameters.

OperationResult
recv_before(&receiver, &deadline, &cancel)Result[T, WaitError]
send_before(&sender, value, &deadline, &cancel)Result[(), SendBeforeError[T]]
select_recv_before(&first, &second, &deadline, &cancel)Result[Selected[A, B], WaitError]
future_before(&future, &deadline, &cancel)Result[(), WaitError]
cancelled_before(&token, &deadline, &cancel)Result[(), WaitError]

WaitError has TimedOut, Cancelled and Disconnected (channels only). Every SendBeforeError[T] variant (TimedOut, Cancelled, Disconnected) returns the original unsent value, without copying it. Cancellation visible before an attempt wins even over readiness. Otherwise an already-ready value or disconnection wins over an expired deadline. Selection keeps the builtin’s first-receiver priority. Future observation leaves its result owned by the future and does not cancel its worker. Token observation leaves its state intact.

Channel and future primitives cannot wake on a separate token, so these wrappers sleep in slices of at most 10 ms, rechecking cancellation between slices. This is a polling interval, not a hard latency guarantee under scheduler or lock contention. Positive fractional milliseconds round up when passed to the native wait, so a final slice can overrun by less than 1 ms. No deadline is restarted.

Low-level millisecond methods have explicit unit names: send_timeout_ms, recv_timeout_ms, wait_timeout_ms on futures and tokens, and select_recv_timeout_ms. The original _timeout spellings remain compatibility aliases with the same millisecond units. They do not accept a separate token.

Mutex[T]

A value that workers update through shared references or Shared[Mutex[T]] handles, one lock holder at a time. See mutexes for the rules and the deadlocks the compiler rejects.

from std.sync import Mutex

class Stats:
    count: i64
    total: i64

def record(stats: &Mutex[Stats], value: i64) -> Result[(), OverflowError[i64]]:
    with stats.lock() as data:
        data.count = data.count + 1
        data.total = data.total + value
    Ok(())

def main() -> Result[(), SpawnError[()] | IoError | OverflowError[i64]]:
    stats = Mutex[Stats](Stats(0, 0))
    with spawn(record, &stats, 3)? as first, spawn(record, &stats, 4)? as second:
        first.join()?
        second.join()?
    final = stats.into_inner()
    print(final.total)?
    Ok(())
7

The explicit joins handle each worker’s arithmetic Result; merely leaving a spawn scope waits for completion but does not propagate the worker’s error. An arithmetic failure unlocks the mutex on exit, retaining earlier mutations.

OperationResultEffect
Mutex[T](value), Mutex(value)Mutex[T]Store value unlocked, inline
with mutex.lock() as value:binds value: &mut TWait for the lock, hold it for the block
mutex.into_inner()TConsume an owned mutex and return its value

T is any type a generic class field can hold: no references, generators, or borrowed environments. A mutex is affine, with no copy, equality, or formatting. It is stored inline (a 32-bit state word beside T); construction, locking, and unlocking allocate nothing. An uncontended lock and unlock are inline atomic instructions; a contended one waits in the kernel.

lock() is valid only as a with manager. It borrows the mutex shared, so no mut is needed, and every exit from the block unlocks. There is no try_lock, timed lock, poisoning, reentrant locking, or fairness guarantee. into_inner() needs no lock: owning the mutex proves no other thread can hold it.

Atomic integers and booleans are in std.atomic. The shared owner Shared[T] is a prelude type. Semaphores, condition variables, and read-write locks are not exported.

Acknowledgements

  • Mutex uses mutex 2 from Ulrich Drepper’s “Futexes Are Tricky”.
  • Uses the Rust libc crate for the target-specific Linux futex syscall number (MIT OR Apache-2.0).