pub trait Actor:
Sized
+ Send
+ 'static {
type Args: Send;
type Error: Send + Debug;
// Required method
fn on_start(
args: Self::Args,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Result<Self, Self::Error>> + Send;
// Provided methods
fn on_run(
&mut self,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Result<(), Self::Error>> + Send { ... }
fn on_stop(
&mut self,
actor_ref: &ActorRef<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_run, 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::Failedwithphaseset toFailurePhase::OnStartandactorset toNone. -
Errors in
on_run: The actor terminates during runtime. The error is captured inActorResult::Failedwithphaseset toFailurePhase::OnRunandactorcontains the actor instance. -
Errors in
on_stop: The actor fails during cleanup. The error is captured inActorResult::Failedwithphaseset toFailurePhase::OnStopandactorcontains 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§
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 or spawn_with_mailbox_capacity.
§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, which can be stored in the actor for self-reference or for initializing child actors
§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, impl_message_handler, 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;
async fn on_start(name: Self::Args, actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error> {
// Create and return the actor instance
Ok(Self { name })
}
}
// Register message handlers
impl_message_handler!(SimpleActor, []);
// 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 { error, phase, .. } => {
println!("Actor failed in phase {:?}: {}", phase, error);
// The `phase` field indicates which lifecycle method caused the failure
// See [`FailurePhase`](crate::FailurePhase) enum for possible values
}
}
Ok(())
}Provided Methods§
Sourcefn on_run(
&mut self,
actor_ref: &ActorRef<Self>,
) -> impl Future<Output = Result<(), Self::Error>> + Send
fn on_run( &mut self, actor_ref: &ActorRef<Self>, ) -> impl Future<Output = Result<(), Self::Error>> + Send
The primary task execution logic for the actor, designed for iterative execution.
The main processing loop for the actor. This method is called repeatedly after on_start completes.
If this method returns Ok(()), it will be called again, allowing the actor to process
ongoing or periodic tasks. The actor continues running as long as on_run returns Ok(()).
To stop the actor normally from within on_run, call actor_ref.stop()
or actor_ref.kill().
on_run's execution is concurrent with the actor's message handling capabilities,
enabling the actor to perform its primary processing while continuing to
respond to incoming messages in its mailbox - a key aspect of the actor model.
§Key characteristics:
-
Lifecycle Management: The actor continues its lifecycle by repeatedly executing the
on_runmethod. Ifon_runreturnsOk(()), it will be called again, enabling continuous processing. This supports the actor model’s concept of independent, long-lived entities. For normal termination, useactor_ref.stop()oractor_ref.kill(). -
State Persistence Across Invocations: Because
on_runcan be invoked multiple times by the runtime (each time generating a newFuture), any state intended to persist across these distinct invocations must be stored as fields within the actor's struct (self). Local variables declared insideon_runare ephemeral and will not be preserved ifon_runcompletes and is subsequently re-invoked. -
Concurrent Message Handling: The
Futurereturned byon_runexecutes concurrently with the actor's message processing loop. This allows the actor to perform itson_runtasks while simultaneously remaining responsive to incoming messages. -
Full State Access:
on_runhas full mutable access to the actor's state (self). Modifications toselfwithinon_runare visible to subsequent message handlers and futureon_runinvocations. -
Essential Await Points: The
Futurereturned byon_runmust yield control to the Tokio runtime via.awaitpoints, especially within any internal loops. Lacking these, theon_runtask could block the actor's ability to process messages or perform other concurrent activities.
§Common patterns:
- Periodic tasks: For executing work at regular intervals. The
on_runfuture would typically involve a single tick of an interval. TheIntervalitself should be stored as a field in the actor's struct to persist acrosson_runinvocations.async fn on_run(&mut self, actor_ref: &ActorRef<MyActor>) -> std::result::Result<(), Self::Error> { // Note: Return type is Result<(), Self::Error> // self.interval is stored in the MyActor struct. self.interval.tick().await; // This await point allows message processing. // Perform the periodic task here. println!("Periodic task executed by actor {}", actor_ref.identity()); self.ticks_done += 1; // If your task is computationally intensive, ensure you still have an await // or offload it (e.g., using [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)). if self.heavy_computation_needed() { // Example: Offload heavy work if truly blocking // let _ = tokio::task::spawn_blocking(|| { /* heavy work */ }).await?; // Or, if it\'s async but long-running, ensure it yields: tokio::task::yield_now().await; } // To stop the actor normally from within on_run, call actor_ref.stop() or actor_ref.kill(). // For example, to stop after 10 ticks: if self.ticks_done >= 10 { println!("Actor {} stopping after {} ticks.", actor_ref.identity(), self.ticks_done); actor_ref.stop().await?; // or actor_ref.kill()? // After calling stop/kill, on_run might not be called again as the actor shuts down. // It\'s good practice to return Ok(()) here, or handle potential errors from stop(). return Ok(()); } // Return Ok(()) to have on_run called again by the runtime for the next tick. // If an Err is returned, the actor stops due to an error. Ok(()) }
§Termination:
- If the
Futurereturned byon_runcompletes withOk(()), the actor continues running, and the runtime will invokeon_runagain to get the nextFuturefor execution. - If the
Futurecompletes withErr(_), the actor terminates due to an error. See theError Handlingsection in theActortrait documentation for details on how errors are handled.
To stop the actor normally from within on_run (e.g., graceful shutdown),
the actor should explicitly call actor_ref.stop().await? or actor_ref.kill().
After such a call, on_run is unlikely to be invoked again by the runtime,
as the actor will be in the process of shutting down.
The actor_ref parameter is a reference to the actor's own ActorRef.
It can be used, for example, to call actor_ref.stop() or actor_ref.kill()
to initiate actor termination from within on_run.
The default implementation of on_run is a simple async block that sleeps for 1 second
and then returns Ok(()), causing it to be called repeatedly until the actor is
explicitly stopped or killed.
Sourcefn on_stop(
&mut self,
actor_ref: &ActorRef<Self>,
killed: bool,
) -> impl Future<Output = Result<(), Self::Error>> + Send
fn on_stop( &mut self, actor_ref: &ActorRef<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 only when the actor is being explicitly stopped, either through a call
to ActorRef::stop (graceful termination) or
ActorRef::kill (immediate termination). It is not called
if the actor stops due to other errors that occurred during message processing or in on_run.
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_ref: &ActorRef<Self>, killed: bool) -> std::result::Result<(), Self::Error> {
if killed {
println!("Actor {} is being forcefully terminated, performing minimal cleanup", actor_ref.identity());
// Perform minimal, fast cleanup
} else {
println!("Actor {} is gracefully shutting down, performing full cleanup", actor_ref.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", so this trait is not object safe.