Scripting & runtime · concurrency
Threads, mutexes, async functions & futures.
Nift 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 supported callable value (including named functions and lambdas) 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.
Transferable ordinary values and closure bindings are copied into isolated worker contexts. Non-transferable resources, including timers, are rejected at the transfer boundary. Explicit mutex/atomic handles intentionally share state; isolation is not a proof against every misuse of native resources.
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.
timer() creates a monotonic stopwatch handle. Control it with start(), pause(), resume(), stop() and reset(), and read the elapsed milliseconds with elapsed(). Timers are per-owner resources and cannot be moved between workers.