Skip to main content

Actor

Trait Actor 

Source
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_weak: &ActorWeak<Self>,
    ) -> impl Future<Output = Result<bool, 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_run, on_stop) can return errors. How these errors are handled depends on when they occur:

  1. Errors in on_start: The actor fails to initialize. The error is captured in ActorResult::Failed with phase set to FailurePhase::OnStart and actor set to None.

  2. Errors in on_run: The actor terminates during runtime. The error is captured in ActorResult::Failed with phase set to FailurePhase::OnRun and actor contains the actor instance.

  3. Errors in on_stop: The actor fails during cleanup. The error is captured in ActorResult::Failed with phase set to FailurePhase::OnStop and actor contains the actor instance.

When awaiting the completion of an actor, check the ActorResult to determine the outcome and access any errors:

Implementors of this trait must also be Send + 'static.

Required Associated Types§

Source

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.

Source

type Error: Send + Debug

The error type that can be returned by the actor's lifecycle methods. Used in on_start, on_run, and on_stop.

Required Methods§

Source

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 type Self::Args) provided when the actor is spawned
  • actor_ref: A reference to the actor’s own ActorRef, which can be stored in the actor for self-reference or for initializing child actors
§Returns
  • Ok(Self): A fully initialized actor instance
  • Err(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;

    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 { 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§

Source

fn on_run( &mut self, actor_weak: &ActorWeak<Self>, ) -> impl Future<Output = Result<bool, Self::Error>> + Send

The idle handler for the actor, similar to on_idle in traditional event loops.

This method is called when the actor’s message queue is empty, allowing the actor to perform background processing or periodic tasks. The return value controls whether on_run continues to be called:

  • Ok(true): Continue calling on_run (equivalent to G_SOURCE_CONTINUE in GTK+)
  • Ok(false): Stop calling on_run, only process messages (equivalent to G_SOURCE_REMOVE)
  • Err(e): Terminate the actor with an error
§Key characteristics:
  • Idle Processing: on_run is only called when the message queue is empty, thanks to the biased selection in the runtime loop. Messages always have higher priority than idle processing.

  • Dynamic Control: The return value allows dynamic control over idle processing. Return Ok(true) to continue, or Ok(false) when idle processing is no longer needed.

  • State Persistence Across Invocations: Because on_run can be invoked multiple times by the runtime (each time generating a new Future), any state intended to persist across these distinct invocations must be stored as fields within the actor’s struct (self). Local variables declared inside on_run are ephemeral and will not be preserved if on_run completes and is subsequently re-invoked.

  • Full State Access: on_run has full mutable access to the actor’s state (self). Modifications to self within on_run are visible to subsequent message handlers and future on_run invocations.

  • Essential Await Points: The Future returned by on_run must yield control to the Tokio runtime via .await points, especially within any internal loops. Lacking these, the on_run task could block the actor’s ability to process messages or perform other concurrent activities.

§Common patterns:
  1. Periodic tasks: For executing work at regular intervals.

    async fn on_run(&mut self, actor_weak: &ActorWeak<MyActor>) -> std::result::Result<bool, Self::Error> {
        self.interval.tick().await;
        println!("Periodic task executed by actor {}", actor_weak.identity());
        self.ticks_done += 1;
    
        // Stop idle processing after 10 ticks, but actor continues processing messages
        if self.ticks_done >= 10 {
            return Ok(false);
        }
    
        Ok(true)  // Continue calling on_run
    }
  2. One-time initialization then message-only: Perform setup work, then only handle messages.

    async fn on_run(&mut self, _: &ActorWeak<Self>) -> std::result::Result<bool, Self::Error> {
        if !self.initialized {
            // Perform one-time initialization
            self.initialized = true;
        }
        Ok(false)  // No more idle processing needed
    }
§Default Implementation

The default implementation returns Ok(false), meaning on_run is called once and then disabled. Actors that don’t override on_run will only process messages.

Source

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:

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 (via stop() call)
  • killed = true: The actor is being forcefully terminated (via kill() 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", so this trait is not object safe.

Implementors§