The Standard Library and Ownership
The public ergonomics depend on ordinary Rust ownership rules being made visible at a few deliberate boundaries.
What the caller owns
- The backend value is moved into
WorkerBuilder::backend, then into the worker. - The handler function or service is moved into
build. - Each job’s
Argsis moved into the handler’s first argument. - Values passed through
.data(value)must beClone + Send + Sync + 'static. The layer clones the value into task extensions; large shared state is usuallyArc<State>. - Extractors inspect
&Taskbriefly, but return owned values for the handler. WorkerContextis cheap to clone because its shared internals useArc.- Retry policies clone
Task, so retryable payload, connection, and identifier types must satisfy the correspondingClonebounds.
The user normally does not lend &mut application state across .await.
If shared mutation is necessary, the state explicitly contains a concurrency
primitive such as Arc<Mutex<T>>, a pool, or a channel sender.
Small standard-library tools with architectural weight
Arc lets cloned tasks and contexts share execution metadata. Atomics represent
monotonic shutdown, status, and in-flight counters without one giant lock.
Mutex<Option<Waker>> stores the wakeup needed to turn shutdown state into a
Future. PhantomData lets WorkerBuilder carry its not-yet-runtime type state
without storing dummy payloads. Pin and pinned drop make task accounting safe
when futures are cancelled.
Clone is a policy, not a convenience
Cloning an Arc<App> shares identity cheaply. Cloning a Task for retry means
replaying its payload is semantically permitted. Apalis puts that distinction in
trait bounds: a non-cloneable task can execute once, but cannot silently enter a
retry mechanism that requires a second owned request.