pub trait Actor:
Sized
+ Send
+ 'static {
type Args: Send;
type Error: Send + Display + Debug;
type IdleEvent: Send + 'static;
// Required method
fn on_start(
args: Self::Args,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send;
// Provided methods
fn on_idle(
&mut self,
event: Self::IdleEvent,
actor_weak: &ActorWeak<Self>,
) -> impl Future<Output = Result<(), Self::Error>> + Send { ... }
fn on_stop(
&mut self,
actor_weak: &ActorWeak<Self>,
killed: bool,
) -> impl Future<Output = Result<(), Self::Error>> + Send { ... }
}Expand description
Defines the behavior of an actor.
Actors are fundamental units of computation that communicate by exchanging messages. Each actor has its own state and processes messages sequentially.
§Error Handling
Actor lifecycle methods (on_start, on_idle, on_stop) can return errors. How these errors
are handled depends on when they occur:
-
Errors in
on_start: The actor fails to initialize. The error is captured inActorResult::FailedasActorFailure::OnStart— no actor instance exists. -
Errors in
on_idle: The actor terminates during runtime. The error is captured inActorResult::FailedasActorFailure::OnIdle, carrying the actor instance. -
Errors in
on_stop: The actor fails during cleanup. The error is captured inActorResult::FailedasActorFailure::OnStop, carrying the actor instance.
When awaiting the completion of an actor, check the ActorResult to determine
the outcome and access any errors:
- Use methods like
is_failed,is_runtime_failed, etc. to identify the error type - Access the error via
errororinto_errorto retrieve error details - If the actor instance is available (
has_actorreturns true), you can recover it usingactororinto_actorfor further processing
Implementors of this trait must also be Send + 'static.
Required Associated Types§
Sourcetype Args: Send
type Args: Send
Type for arguments passed to on_start for actor initialization.
This type provides the necessary data to create an instance of the actor.
Sourcetype Error: Send + Display + Debug
type Error: Send + Display + Debug
The error type that can be returned by the actor’s lifecycle methods.
Used in on_start, on_idle, and on_stop.
Requires Display (in addition to Debug) so a failure surfaced through
ActorResult renders as a human-readable message
({}) instead of only the {:?} debug form. std::error::Error is
intentionally not required, so plain types like String stay usable.
Sourcetype IdleEvent: Send + 'static
type IdleEvent: Send + 'static
Event type carried by streams subscribed via
ActorRef::subscribe_idle and dispatched
to on_idle.
Actors that never subscribe an idle stream should set this to (). The
#[derive(Actor)] macro fills this in automatically. When implementing
Actor manually, declare it explicitly:
impl Actor for MyActor {
type Args = ();
type Error = anyhow::Error;
type IdleEvent = ();
// ...
}Required Methods§
Sourcefn on_start(
args: Self::Args,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send
fn on_start( args: Self::Args, actor_ref: &ActorRef<Self>, ) -> impl Future<Output = Result<Self, Self::Error>> + Send
Called when the actor is started. This is required for actor creation.
This method is the initialization point for an actor and a fundamental part of the actor model design.
Unlike traditional object construction, the actor’s instance is created within this asynchronous method,
allowing for complex initialization that may require awaiting resources. This method is called by
spawn, spawn_with_mailbox_capacity,
or spawn_with_options.
§Actor State Initialization
In the actor model, each actor encapsulates its own state. Creating the actor inside on_start
ensures that:
- The actor’s state is always valid before it begins processing messages
- The need for
Option<T>fields is minimized, as state can be fully initialized - Asynchronous resources (like database connections) can be acquired during initialization
- Initialization failures can be cleanly handled before the actor enters the message processing phase
§Parameters
args: Initialization data (of typeSelf::Args) provided when the actor is spawnedactor_ref: A reference to the actor’s ownActorRef— useful for handing to child actors that need a handle back to this one, or for subscribing idle streams. Do not store a strong clone of it in the actor’s own state. The runtime task owns the actor instance, so an instance that owns a strong self-reference forms a permanent keep-alive cycle: the ref-drop shutdown documented onon_stopcan never fire,on_stopnever runs, theJoinHandlenever resolves, and once every external ref is dropped there is no handle left to callstop()/kill()with — the actor task leaks for the life of the runtime. For self-reference, storeActorRef::downgrade(actor_ref)(anActorWeak) and upgrade per use — the same rule documented onsubscribe_idlefor captured streams
§Returns
Ok(Self): A fully initialized actor instanceErr(Self::Error): If initialization fails
If this method returns an error, the actor will not be created, and the error
will be captured in the ActorResult with
FailurePhase::OnStart.
§Example
use rsactor::{Actor, ActorRef, Message, spawn, ActorResult};
use std::time::Duration;
use anyhow::Result;
// Simple actor that holds a name
#[derive(Debug)]
struct SimpleActor {
name: String,
}
// Implement Actor trait with focus on on_start
impl Actor for SimpleActor {
type Args = String; // Name parameter
type Error = anyhow::Error;
type IdleEvent = ();
async fn on_start(name: Self::Args, actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error> {
// Create and return the actor instance
Ok(Self { name })
}
}
// Main function showing the basic lifecycle
#[tokio::main]
async fn main() -> Result<()> {
// Spawn the actor with a name argument using the [`spawn`](crate::spawn) function
let (actor_ref, join_handle) = spawn::<SimpleActor>("MyActor".to_string());
// Gracefully stop the actor using [`stop`](crate::actor_ref::ActorRef::stop)
actor_ref.stop().await;
// Wait for the actor to complete and get its final state
// The JoinHandle returns an [`ActorResult`](crate::ActorResult) enum
match join_handle.await? {
ActorResult::Completed { actor, killed } => {
println!("Actor '{}' completed. Killed: {}", actor.name, killed);
// The `actor` field contains the final actor instance
// The `killed` flag indicates whether the actor was stopped or killed
}
ActorResult::Failed { failure, .. } => {
println!("Actor failed in phase {:?}: {}", failure.phase(), failure.error());
// `failure.phase()` indicates which lifecycle method caused the failure
// See [`FailurePhase`](crate::FailurePhase) enum for possible values
}
}
Ok(())
}Provided Methods§
Sourcefn on_idle(
&mut self,
event: Self::IdleEvent,
actor_weak: &ActorWeak<Self>,
) -> impl Future<Output = Result<(), Self::Error>> + Send
fn on_idle( &mut self, event: Self::IdleEvent, actor_weak: &ActorWeak<Self>, ) -> impl Future<Output = Result<(), Self::Error>> + Send
Handler invoked for each event yielded by streams subscribed via
ActorRef::subscribe_idle.
The runtime owns a SelectAll of every subscribed
stream and polls it as one branch of its select! loop. When any stream yields an
event, on_idle is called with &mut self and full mutable access to actor state —
no concurrent borrow with message handlers, no surprise cancellation of work in
progress. A returned Err terminates the actor with
FailurePhase::OnIdle.
§Why a stream — not a free-form async block
In rsActor 0.15 and earlier, idle work was expressed via on_run, an async method the
runtime called repeatedly inside select!. Each iteration produced a new future, so
any timing or async state created inside on_run (e.g. a raw tokio::time::sleep)
was dropped whenever a message arrived — a 1-second sleep that races with frequent
messages may never fire.
Subscription-based idle events move the timing/event state out of the cancellable
future and into a Stream owned by the runtime. Stream
implementations like IntervalStream are
cancel-safe by construction: even when the surrounding select! arm is cancelled,
the stream’s internal schedule survives across iterations.
§Priority relative to messages
The runtime’s select! is biased with this order: terminate signal, priority
mailbox, new subscriptions, regular mailbox, idle events. Idle events therefore
have lower priority than messages — high message throughput can starve idle
processing. This mirrors the previous on_run semantics. If you need strict
fairness, send self-tells from a separate task instead.
§Common patterns
Periodic tick: subscribe an IntervalStream
in on_start and react in on_idle.
async fn on_idle(&mut self, _: (), actor_weak: &ActorWeak<Self>) -> Result<(), Self::Error> {
self.ticks += 1;
println!("tick {} on actor {}", self.ticks, actor_weak.identity());
Ok(())
}Dynamic subscription: add a new idle source from a message handler.
async fn handle_start_sensor(&mut self, _: StartSensor, actor_ref: &ActorRef<Self>) {
actor_ref
.subscribe_idle(self.sensor.events().map(MyEvent::SensorTick))
.ok();
}§Opt-in
on_idle is only driven when the actor is spawned with the idle channel enabled
via SpawnOptions::with_idle. The channel is off by
default so that actors which never use idle events do not pay for an extra always-active
branch in the runtime’s select! loop. Without it,
subscribe_idle returns
Error::IdleChannelNotEnabled and this method is
never called.
§Default implementation
The default returns Ok(()) immediately. Actors that never subscribe an idle stream
can leave this unimplemented and set type IdleEvent = ();.
Sourcefn on_stop(
&mut self,
actor_weak: &ActorWeak<Self>,
killed: bool,
) -> impl Future<Output = Result<(), Self::Error>> + Send
fn on_stop( &mut self, actor_weak: &ActorWeak<Self>, killed: bool, ) -> impl Future<Output = Result<(), Self::Error>> + Send
Called when the actor is about to stop. This allows the actor to perform cleanup tasks.
This method is called when the actor is stopping, including:
- Explicit stop via
ActorRef::stop(graceful termination) - Explicit kill via
ActorRef::kill(immediate termination) - Implicit shutdown when every strong
ActorRefhas been dropped (treated as graceful termination, sokilled = false) - Cleanup after an
on_idleerror
It is not called if the actor fails during message processing (handler panic/error).
The result of this method affects the final ActorResult returned when awaiting the join handle.
The killed parameter indicates how the actor is being stopped:
killed = false: The actor is stopping gracefully (viastop()call)killed = true: The actor is being forcefully terminated (viakill()call)
Cleanup operations that should be performed regardless of how the actor terminates
should be implemented as a Drop implementation on the actor struct instead.
§Example
async fn on_stop(&mut self, actor_weak: &ActorWeak<Self>, killed: bool) -> std::result::Result<(), Self::Error> {
if killed {
println!("Actor {} is being forcefully terminated, performing minimal cleanup", actor_weak.identity());
// Perform minimal, fast cleanup
} else {
println!("Actor {} is gracefully shutting down, performing full cleanup", actor_weak.identity());
// Perform thorough cleanup
}
Ok(())
}The identity method provides a unique identifier for the actor.
For a complete lifecycle example, see the example in on_start that demonstrates
actor creation with spawn, graceful termination with stop,
and handling the ActorResult from the join handle.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".