Actor Lifecycle and Supervision
Actors outlive individual calls, so their construction, cleanup, and recovery are explicit.
A task belongs to the function or scope that started it. An actor has its own lifetime: spawning it creates state and a mailbox, and returning from the spawning function does not by itself stop the actor. This independence is useful for services, caches, and coordinators, but it also means shutdown and recovery need names.
Follow the normal path
- Construct:
spawnevaluates the supplied fields and runsinitwhen the actor declares one. No handler observes partly initialized state. - Start: one
#[on(start)]hook may prepare state after construction or restart. - Handle messages: each
receive fngets exclusive access to the actor's state for its turn, including across suspension. - Request shutdown:
stop(actor)asks for a graceful stop and returns immediately. - Settle cleanup:
stopped(actor)waits until terminal hooks, state cleanup, and owned resource release have finished.
terminate(actor) requests cancellation instead of a graceful stop. It also returns immediately. Use it when the operation calls for cancellation, not as a shorter spelling of stop followed by stopped.
Start and stop an actor
actor Reporter {
var reports: i64 = 0;
#[on(start)]
fn ready() {
println("reporter ready");
}
receive fn record() -> i64 {
reports += 1;
reports
}
#[on(stop)]
fn finish() {
println(f"reports written: {reports}");
}
}
fn main() {
let reporter = spawn Reporter;
println(reporter.record().expect("reporter available"));
stop(reporter); // request graceful shutdown
stopped(reporter); // wait for finish and state cleanup
}A handler call waits for that handler's turn, not for the actor's whole lifetime. The explicit stopped call is the point at which main knows terminal cleanup has settled. Calling stopped alone can observe a stop requested elsewhere.
Resources follow ownership
Actor fields can own ordinary values and affine resources. Terminal cleanup releases what the actor still owns after its handlers and scoped tasks have settled. A fault may bypass a normal stop hook, but it does not erase the ownership cleanup obligation. See resources and linear values for the two contracts.
Separate failure from recovery
Three permanent children, declared in order A → B → C. The strategy determines which children restart together.
An actor fault ends the current incarnation. A supervisor owns named child roles and decides whether a failed child should be restarted. Its strategy describes which related children restart, while its intensity clause limits repeated restarts within a time window.
one_for_onerestarts the failed child.one_for_allrestarts every child.rest_for_onerestarts the failed child and those declared after it, which can model dependency order.
Each child also has a restart classification: permanent restarts after any exit, transient after abnormal exit, and temporary never restarts. Choose a policy from the service's recovery needs; restart is not a substitute for deciding where durable application data lives.
actor Worker {
let id: i64;
receive fn work(value: i64) -> i64 { value + id }
}
supervisor Service {
strategy: one_for_one;
intensity: 5 within 10s;
child primary: Worker { id: 1 } restart: permanent;
}
fn main() {
let service = spawn Service;
let worker = service.primary;
println(worker.work(41).expect("worker available"));
stop(service);
stopped(service);
}Understand roles and incarnations
A supervised child name such as service.primary is a stable role. Calls through the role resolve its current incarnation after a restart. A direct incarnation handle remains tied to the actor instance it named; restarting a role does not turn a stale direct handle into the replacement.
restarted(role) waits for an already pending replacement to settle. It does not initiate a restart. A restarted actor is reconstructed from its child declaration and startup path, so state that must survive a crash belongs in another durable component.
Advanced: runtime states versus source-level lifecycle
The runtime tracks scheduling states such as idle, runnable, running, suspended, stopping, crashing, crashed, stopped, and sleeping. Most application code should reason through the source operations above: handler completion, stop, terminate, stopped, and supervisor policy. The internal scheduling state is not a public synchronization API.
A request deadline also does not stop an actor. If a request expires before dispatch, the handler does not start. Once claimed, the actor owns the handler turn; a caller that stops waiting does not roll back its effects.
Continue from here
- Read the actor guide's supervision examples for restart strategies and nested trees.
- Look up exact policies and lifecycle APIs in the released supervision reference.
- Compare the model with Erlang/OTP in the supervision comparison.
- Use Structured Concurrency for child work whose lifetime belongs to a call or scope.