Actors

Actors own state and run one handler at a time, including across suspension. A call waits for completion. A void handler still returns a completion outcome; use match, ? or handle to deal with failure. The message-passing concepts guide develops the isolation model before this page's syntax and delivery details.

actor Counter {
    var count: i64 = 0;
    receive fn increment(n: i64) { count = count + n; }
    receive fn total() -> i64 { count }
}
fn main() {
    let counter = spawn Counter;
    counter.increment(42) handle error {
        println(f"increment failed: {error}");
        return;
    };
    match counter.total() {
        .Ok(value) => println(value),
        .Err(error) => println(f"query failed: {error}"),
    }
    stop(counter);
    stopped(counter);
}

fork counter.total() creates a task; await task joins its result. mailbox(target, on_full: ...) submits without waiting for completion and refuses value-returning handlers. policy(target, on_full: .Wait) or the .Reject policy controls admission for completion calls; a bare handle waits for room.

Completion errors are ActorError<E, Req>. A rejecting completion view or mailbox submission returns Rejected(SendFailure<Req>) only when the request was definitely not admitted. Its message supports retry() and to(target). Other errors can leave remote admission or completion uncertain and do not return a request to retry. Remote mailbox success acknowledges destination admission, not handler completion.

Isolation and backpressure

Messages must satisfy Send. Ordinary value data is snapshotted at the actor boundary, while affine owners such as resources and stream halves move. Actor state is accessed only by that actor's handlers, and each actor runs one handler at a time.

An actor without a mailbox declaration has unbounded capacity. mailbox 1024; sets a bound and defaults to waiting for space. Declaration policies include drop_new, drop_old, fail, and coalesce(key) fallback .... Coalescing applies only to submissions for a unit-returning handler and replaces older queued work with the same handler and key. Completion calls are never discarded or superseded.

A fails handler returns Failed(E) to a completion caller. When submitted through a mailbox view, its error faults the actor with its Display text.

stop(actor) requests a graceful stop; stopped(actor) waits for terminal cleanup. terminate(actor) requests cancellation. Inside an actor, self is its handle and self.field accesses state. An actor's handle has that actor's type T.

Periodic handlers

#[every(duration)] schedules a zero-argument handler through the actor's normal mailbox. It starts with the actor and stops when the actor stops, so periodic work never runs concurrently with another handler on that actor.

actor Heartbeat {
    var ticks: i64 = 0;
    #[every(1s)]
    receive fn tick() { ticks += 1; }
    receive fn count() -> i64 { ticks }
}

A periodic handler has no parameters and unit success. On WASI its timer is cooperative; see target capabilities.

Lambda actors

A lambda actor handles one message type. Its call waits for completion like a named actor call, and its handle type is actor(Message) -> Reply.

fn main() {
    let double: actor(i64) -> i64 = actor |value: i64| -> i64 {
        value * 2
    };
    match double(21) {
        .Ok(value) => println(value),
        .Err(error) => println(f"worker failed: {error}"),
    }
    stop(double);
    stopped(double);
}

actor move |value| { ... } requests transfer of captured owners. Ordinary value captures already become independent actor state. Use a named actor for several handlers, recursive protocols, trait implementation, or a named supervisor child.

Interceptors

One #[intercept] method can inspect each receive delivery around the handler. It consumes its Call<Self> exactly once with proceed() or refuse(reason) and returns the resulting Outcome<Self>. Supervisor interceptors wrap child interceptors from the outside in. Refusal is an actor error for completion calls and does not crash the actor.

Interceptors cover receive handlers, stream starts, and periodic ticks. Lifecycle hooks, system messages, replies, and stream items bypass them.

Supervised children and admitted remote actors also have the actor's own type. A supervised reference follows its stable role through restarts. is compares role identity for roles and exact incarnation identity for direct or imported references; a role differs from its current occupant. restarted(actor) waits only for an already pending role replacement to settle, returning immediately for direct and imported references without asserting liveness.