Scripting & runtime · concurrency
Threads, mutexes, async functions & futures.
Nift v4.5 runs real native work concurrently. Use explicit threads for dedicated work, async functions for pool-scheduled tasks, and mutexes or atomics only when workers intentionally share state.
Native threads
fn(add(a, b)) { return a + b }
t := thread(add, 20, 22)
print(t.join())
print(t.join()) // replayable: 42 again thread(callable, ...args) accepts a named function or lambda and starts a native worker. join() waits and replays the same result on later calls; worker errors are rethrown at join. done() and status() inspect progress, and hardware_concurrency() reports the host hint. Thread handles own safe teardown: a runtime does not abandon work it created.
Ordinary values and captured closures are copied into an isolated worker context. That makes accidental shared mutation impossible; pass a mutex or atomic handle when sharing is intentional.
Mutex-protected shared state
fn(inc(m, count)) {
i := 0
while(i < count) {
m.lock()
m.set(m.get() + 1)
m.unlock()
i++
}
return true
}
counter := mutex(0)
a := thread(inc, counter, 500)
b := thread(inc, counter, 500)
a.join(); b.join()
counter.lock(); print(counter.get()); counter.unlock() mutex() creates a lock-only handle; mutex(value) also owns a synchronized value. The API is lock(), try_lock(), unlock(), locked(), plus owner-only get()/set(). Locks are non-recursive. Reading the protected value without owning the lock, recursively locking it, or unlocking from another worker is an error.
Async functions and futures
fn[async](double(x)) { return x * 2 }
triple := async (x) => { return x * 3 }
f := double(21)
print(type(f)) // future
print(await f) // 42
print(await triple(14)) In template syntax the named declaration is @fn[async](double(x)) { ... }. Calling an async function immediately returns a future. Prefix await accepts a future expression directly; a bare await f statement is also valid. Results are replayable, failures propagate at await, and done()/status() inspect state.
Async functions use a bounded native worker pool. A worker awaiting another future helps drain queued work, so nested awaits make progress instead of exhausting a fixed pool. Runtime teardown waits for work created by that runtime. Use thread(...) for an explicit native thread; use async functions for tasks scheduled onto the shared bounded pool.
async(work, ...), await(f), and f.await() were superseded before release. Use async function/lambda declarations and prefix await.