Consuming thread jobs
with spawn(job)? as task: moves a concrete wholly owned zero-argument closure
into a worker. Reusable and one-shot closures are accepted; def once permits
moving a capture into the result. Borrowed captures and generator frames are
excluded. A named start with owned arguments binds
them into such a job. The scoped-thread lifetime and join rules
still apply.
The start expression has result Result[(), SpawnError[F]], where F is the
concrete job type. Its one variant, SpawnError[F].Unavailable(job, code),
returns the complete unstarted job and the i32 native code after any
native start failure. Returning, matching,
dropping, and retrying the error and job allocate nothing. Capture expressions
have already run, but the worker has not. ? widens the error into an enclosing
sum containing SpawnError[F], which keeps the job. A kept job’s endpoints stay
alive through the joins that the ? exit performs, so the
live-endpoint join rule counts them. A generic signature names the job as
F. A nongeneric one writes its signature, such as
SpawnError[OnceClosure[[], list[i64]]], alone or in a sum; ? resolves that
member to the job it propagates, as for a
returned closure.
Each such member names one producer, so start several jobs from one factory
function or a generic helper.
Job-free errors
with spawn(job).drop_job()? as task: gives up the job instead. drop_job()
converts Result[(), SpawnError[F]] to Result[(), SpawnError[()]]: on failure
it drops the job and its captures at the start, and the error keeps its variant
and code, SpawnError[()].Unavailable((), code). When applied directly to
spawn, the compiler constructs this job-free error in the failed-start block,
without materializing an intermediate job-carrying result in the caller frame.
On success it changes nothing. One
SpawnError[()] member covers every job, so a function can start distinct
closures and propagate one precise error:
def main() -> Result[(), IoError | SpawnError[()]]:
with spawn(def once () -> i64: 1).drop_job()? as first:
print(first.join())?
with spawn(def once () -> str: "two").drop_job()? as second:
print(second.join())?
Ok(())
Because the job is gone before ? runs its exit’s joins, its endpoints cannot
block them. The conversion copies a tag and the code and allocates nothing. drop_job() also
applies to a SpawnError[F] value, or a Result carrying SubmitError[F], as
for pool submissions. Erasing the error into Failure drops the
job too, but loses the error type; use it only when no precise error fits.
After success, only the worker owns the captures. One-shot invocation moves them into the body; reusable invocation borrows them once and then drops the environment. Join transfers the result exactly once; automatic join drops an unclaimed result. Job storage and the ownership handoff require no heap allocation.