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

Start With the Caller

Before choosing channels or traits, write the experience we want:

let runner = JobRunner::builder()
    .queue_capacity(100)
    .concurrency_limit(3)
    .start()?;

let handle = runner.submit(async move {
    fetch_user(user_id).await
}).await?;

let user = handle.await?;
runner.shutdown().await;

The two awaits mean different things:

  • submit(...).await waits for queue capacity. Success means the runner accepted ownership of the job.
  • handle.await waits for job completion and returns its typed value or business error.

queue_capacity is clearer than max_jobs: it counts waiting jobs, not running jobs, lifetime submissions, or bytes. Arbitrary futures can own String, Vec, and Arc graphs whose total heap use is not described by size_of_val. Strict memory budgeting would require a different contract such as caller-supplied weights.

Version one accepts futures rather than requiring a Job trait. In-memory work needs no serialization or durable identity, and async move already captures precisely the owned input needed to execute later. A named trait can be layered on as an application convention.