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 stored jobs

Stored reusable callbacks can run on scoped threads. Borrow them explicitly, just as you would borrow a local closure. Distinct tuple slots have separate loans.

def counter(total: i64) -> Closure[[], Result[i64, OverflowError[i64]]]:
    def [mut total]() -> Result[i64, OverflowError[i64]]:
        total = total + 1
        Ok(total)

def main() -> Result[(), SpawnError[()] | IoError | OverflowError[i64]]:
    mut jobs = (counter(10), counter(100))
    with spawn(&mut jobs[0])? as first, spawn(&mut jobs[1])? as second:
        print(first.join()?)?
        print(second.join()?)?
    print(jobs[0]()?)?
    Ok(())
11
101
12

A failed start leaves its borrowed callback owned by the caller. Any earlier successful start is joined.

Workers can also return an owned callback through join():

def make(offset: i64) -> Closure[[i64], Result[i64, OverflowError[i64]]]:
    def [offset](n: i64) -> Result[i64, OverflowError[i64]]:
        Ok(offset + n)

def main() -> Result[(), SpawnError[()] | IoError | OverflowError[i64]]:
    with spawn(make, 10).drop_job()? as task:
        callback = task.join()
        print(callback(3)?)?
    Ok(())
13

List and dictionary entries can also supply a borrowed job. Except at distinct literal list indices, their loans protect the whole collection. Even after join(), wait until the with block ends before resizing or replacing that collection. Stored callbacks have the same thread-eligibility restrictions as local ones.