Kite

sync

std/sync — Mutex and Atomic, for genuinely shared mutable state.

Why this exists even though Kite has no OS threads yet.

It is tempting to argue that a lock is pointless without parallelism, and that argument is wrong. Kite's tasks are cooperative: they interleave at await and task.yield, and anywhere a task yields, another task runs. A read-modify-write that spans a yield —

let n = counter.value // task A reads 5 await something() // task B runs, reads 5, writes 6 counter.value = n + 1 // task A writes 6; B's increment is lost

— is a lost update on one thread, today, with the scheduler that ships. That is the same bug a mutex prevents on twenty cores, and it is why Tokio ships an async Mutex for a runtime that may well be single-threaded.

What real threads will change is the cost of these, not their meaning: a lock that today yields until a flag clears becomes one that parks on a futex, and no source changes.

The type-system half. Share means "deeply immutable, and so safe to move to another task" — a type with a var field anywhere in it is not Share. Mutex<T> has a var field and is Share whatever else T holds, because reaching the value requires taking the lock — unless T holds a JsValue, which a lock cannot carry to another isolate. The compiler knows these two types by name, which is the one carve-out in an otherwise purely structural rule; docs/02 §4 is where that is specified.

Mutex

pub struct Mutex<T>

Mutual exclusion around a value.

The value is inside the lock rather than beside it, which is the whole point: there is no way to name it without holding the lock, so "I forgot to take the lock" is not a mistake that can be written.

mutex

pub fn mutex<T>(value: T) -> Mutex<T>

is_held

pub fn is_held<T>(m: Mutex<T>) -> bool

Whether the lock is currently held. For diagnostics — a program that branches on this has a race between asking and acting.

try_lock

pub fn try_lock<T>(var m: Mutex<T>) -> Option<T>

Take the lock if it is free, and hand back what it guards.

Nil means somebody else holds it. Every caller that takes the lock must [release] it; prefer [update], which cannot be got wrong.

lock

pub async fn lock<T>(var m: Mutex<T>) -> T

Take the lock, yielding until it is free.

Yielding rather than spinning is what makes this work at all under a cooperative scheduler: a spin would never let the holder run, so the lock would never be released and the program would hang rather than wait.

And parking before each yield, as task.race does, is what makes it work when the holder is sleeping. The clock jumps to the next deadline only when every task is waiting, and a task that merely yields is not waiting — it is ready to run again at once. So a waiter that only yielded kept the clock where it was, the holder's task.sleep never ended, the lock was never released, and the two waited on each other forever.

release

pub fn release<T>(var m: Mutex<T>, value: T)

Put a value back and release the lock.

update

pub async fn update<T>(var m: Mutex<T>, f: fn(T) -> T)

Take the lock, transform the value, put it back — as one step.

The shape to reach for. f runs while the lock is held and cannot yield, because it is an ordinary function rather than an async one — so nothing interleaves with it and the read-modify-write is atomic with respect to every other task.

Atomic

pub struct Atomic

An integer that can be read and written as one step.

int only, and deliberately: an atomic of an arbitrary type is a lock wearing a misleading name, and the whole value of the word "atomic" is that the operation cannot be interleaved. Everything here is a single expression with no yield in it, which is exactly that guarantee under the cooperative scheduler and maps to a real atomic instruction under a threaded one.

atomic

pub fn atomic(value: int) -> Atomic

load

pub fn load(a: Atomic) -> int

store

pub fn store(var a: Atomic, value: int)

add

pub fn add(var a: Atomic, delta: int) -> int

Add and return the new value.

compare_swap

pub fn compare_swap(var a: Atomic, expect: int, next: int) -> bool

Set to next only if the current value is expect. Answers whether it was.

The primitive every other lock-free operation is built out of.

swap

pub fn swap(var a: Atomic, next: int) -> int

Replace the value and return what was there.