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

Move jobs to workers

An owned closure can move its inputs into a worker. Use a one-shot closure when the worker also moves those inputs into its result.

def main() -> Result[(), AllocError | IoError | SpawnError[OnceClosure[[], list[i64]]]]:
    values = [3, 4]?
    with spawn(def once [values]() -> list[i64]: values)? as task:
        print(task.join())?
    Ok(())
[3, 4]

If thread creation fails, the error is SpawnError[F].Unavailable(job, code), which retains the complete job and the native error code. F is the closure’s own type. The signature writes OnceClosure[[], list[i64]], and ? fills in the concrete closure it propagates, so main returns the unstarted job unchanged.

A signature names one closure there: a second spawn of a different closure literal is rejected. When you do not need the job back, call drop_job() on each start. It drops a failed start’s job right away, and the error becomes SpawnError[()], which names no job, so one member covers every start:

def main() -> Result[(), AllocError | IoError | SpawnError[()]]:
    values = [3, 4]?
    with spawn(def once [values]() -> list[i64]: values).drop_job()? as first:
        print(first.join())?
    with spawn(def once () -> str: "done").drop_job()? as second:
        print(second.join())?
    Ok(())
[3, 4]
done

Dropping the job also drops the channel endpoints it captured, so they cannot keep a later join waiting; avoid waits that hang shows why that matters.

To keep the job, make each one with one factory function, or use a generic helper, which names the job as F. Recover the job to retry or run it locally, as this helper does:

def attempt[F: OnceCallable[[], i64]](job: F) -> Result[i64, SpawnError[F]]:
    with spawn(job)? as task:
        return Ok(task.join())

def run[F: OnceCallable[[], i64]](job: F) -> i64:
    match attempt(job):
        case Ok(value):
            value
        case Err(SpawnError.Unavailable(job, _)):
            job()

def main() -> Result[(), IoError]:
    n = 42
    job = def once [n]() -> i64:
        n
    print(run(job))?
    Ok(())
42

A reusable closure can also be moved into spawn; its captures are dropped after the worker’s single invocation.