Standard library / worker_threads · on the one-page reference

worker_threads

Experimental — new, manually tested rather than conformance-suite covered.

Real CPU parallelism: a Lumen function runs on its own native OS thread. Node's worker_threads loads a separate script file into an isolated realm — Lumen has no equivalent (one program compiles to one static binary, no second module to dynamically load), so Worker.run takes a Lumen function value instead of a file path, the closest honest adaptation of the same idea. Deliberately a flat, one-shot namespace call (Worker.run(fn)) rather than a new Worker(...) object — Node's class shape exists for a stateful, message-passing, long-lived entity (postMessage/.on('message')/.terminate()) that Lumen has no channel primitive for yet; shipping the constructor without any of the methods it implies would be worse than the flat shape actually shipped.

Worker.run(fn)

(() => T) → Promise<T>, T one of i32 | i64 | f64 | bool — spawns a real, detached OS thread (one Worker = one thread, so a CPU-bound call never queues behind unrelated work), runs fn on it, and resolves the returned Promise once it finishes. fn may be a plain named function (no captures at all) or an arrow capturing only scalar outer bindings — Lumen's existing closure lowering copies each capture by value into a heap-allocated environment at the closure's creation point, so a captured scalar is a true snapshot with no live aliasing back to the caller's binding.

function fib(): i32 {
  let a: i32 = 0;
  let b: i32 = 1;
  let i: i32 = 0;
  while (i < 40) {
    const next = a + b;
    a = b;
    b = next;
    i = i + 1;
  }
  return a;
}

async function main(): Promise<void> {
  const p = Worker.run(fib);
  console.log("worker launched, main keeps going");
  console.log(await p);
}

What's safe to cross the thread boundary, stated narrowly on purpose: only the four scalar types above, checker- enforced — Lumen has no borrow checker and no Send/Sync distinction, so strings, arrays, Map/Set/Buffer, and class instances are deliberately not accepted as T yet (not verified safe without a real deep-copy/capture-safety analysis). This does not, by itself, make every closure safe: capturing a reference-shaped outer binding (an array, Map/Set/Buffer, a class instance) only copies the pointer/slice header into the worker's closure — the pointee is still the same shared, unsynchronized memory. If the main thread (or another Worker) mutates it while the worker reads or writes it too, that's a real data race, the same kind of trade-off http.createServer's concurrent handlers already document above. Not statically prevented this pass. For Map/Set specifically (spec 492) this is no longer silent: each instance detects an overlapping access from another thread and stops with an explicit runtime error instead of corrupting its own storage or crashing somewhere unrelated — still not safe to rely on, but no longer invisible when it happens. Arrays, Buffer, and class instance fields have no such guard yet and remain a real, silent data race.