Documentation channel: current development main at compiler checkpoint 1d7e15e. The latest tagged release is v0.0.3; pages identify APIs that are not yet released.

Concurrency

Strut uses native threads and explicit synchronization. Safe ownership keeps objects alive across threads, while shared mutable state still needs a mutex or channel.

Native threads

Pass a named function and arguments, or pass a lambda. join() waits for completion and rethrows worker errors on the joining thread.

worker := thread(work, value);
worker.join();

Lambda threads

worker := thread(() => {
    print("running on another thread");
});
worker.join();

Mutexes

A mutex supports explicit locking when required.

mutex mtx;
mtx.lock();
counter++;
mtx.unlock();

The scoped form releases the mutex even if the callable exits with an error.

mtx.lock(() => {
    counter++;
});

Channels

channel<T> is a typed blocking queue. receive() waits for a value or for the channel to close and returns T?.

channel<int> jobs;

producer := thread(() => {
    jobs.send(42);
    jobs.close();
});

value := jobs.receive();
producer.join();

Atomic values

atomic<int> and atomic<bool> provide sequentially consistent shared state without a separate mutex. Both support load, store, exchange, and compare_exchange; integer atomics also support fetch_add and fetch_sub. Atomic values cannot be copied or moved: load the value and explicitly initialize another atomic instead.

atomic<int> counter := 0;

function increment() -> void {
    counter.fetch_add(1);
    return;
}

function main() -> int : ThreadError {
    first := thread(increment);
    second := thread(increment);
    first.join();
    second.join();

    println(counter.load());
    println(counter.exchange(8));
    println(counter.compare_exchange(8, 9));
    println(counter.load());
    return 0;
}

Race-safety model

T* lifetime accounting is safe across threads, but that does not make arbitrary mutation of the pointed-to object race-free. Synchronize shared mutable state with atomic<T>, mutex, or channel<T>. Direct T& arguments are rejected at thread boundaries because they are non-owning borrows.