Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run scoped threads

Use with spawn(...) to run a function on a native thread. The current thread continues until join() waits for the result:

def squares(n: i64) -> Result[list[i64], AllocError | OverflowError[i64]]:
    [i * i for i in range(n)]

def main() -> Result[(), SpawnError[()] | AllocError | IoError | OverflowError[i64]]:
    with spawn(squares, 4).drop_job()? as first, spawn(squares, 3).drop_job()? as second:
        print(first.join()?)?
        print(second.join()?)?
    Ok(())
[0, 1, 4, 9]
[0, 1, 4]

The ? after spawn(...) handles failure to create the thread. The ? after join() handles the worker’s own Result, here allocation failure or arithmetic overflow while building its list. They are separate failures. drop_job() is explained in error types at thread start. Join consumes the task binding, so it cannot be called twice.

Every started thread finishes before its with block exits. This includes return, ?, break, and continue. If you omit join(), exit waits and drops the unused result, including any application error. Join when the result matters. A task cannot escape its block or be stored for later.

Scopes cannot forcibly cancel workers. Keep their computations finite or give them a shutdown protocol. Fatal traps terminate the process without unwinding.

Error types at thread start

A failed start reports SpawnError[F], which returns the unstarted job: here the function and its arguments. drop_job() drops that job at the start, so the error becomes SpawnError[()], one member for every start. Keep the job when you want to retry it, as owned worker jobs shows. Borrowed workers report SpawnError[()] too.

Like any ?, the start error widens into the enclosing Result’s error sum, so main lists SpawnError[()] beside the worker’s AllocError and OverflowError[i64], and the printing IoError. A sum without the start error is rejected.

Use module-level error aliases to keep longer signatures readable, as in executor jobs. Each alias names that example’s propagated errors; it does not change widening through ? or introduce an error wrapper.