Crate rsactor

Source
Expand description

§rsActor

A Simple and Efficient In-Process Actor Model Implementation for Rust

rsActor is a lightweight, Tokio-based actor framework in Rust focused on providing a simple and efficient actor model for local, in-process systems. It emphasizes clean message-passing semantics and straightforward actor lifecycle management while maintaining high performance for Rust applications.

§Features

  • Asynchronous Actors: Actors run in their own asynchronous tasks.
  • Message Passing: Actors communicate by sending and receiving messages.
  • Straightforward Actor Lifecycle: Actors have on_start, on_run, and on_stop lifecycle hooks that provide a clean and intuitive actor lifecycle management system. The framework manages the execution flow while giving developers full control over actor behavior.
  • Graceful Shutdown & Kill: Actors can be stopped gracefully or killed immediately.
  • Typed Messages: Messages are strongly typed, and replies are also typed.
  • Macro for Message Handling:
    • message_handlers attribute macro with #[handler] method attributes for automatic message handling (recommended)
  • Type Safety Features: ActorRef<T> provides compile-time type safety with zero runtime overhead
  • Optional Tracing Support: Built-in observability using the tracing crate (enable with tracing feature):
    • Actor lifecycle event tracing (start, stop, different termination scenarios)
    • Message handling with timing and performance metrics
    • Reply processing and error handling tracing
    • Structured, non-redundant logs for easier debugging and monitoring

§Core Concepts

  • Actor: Trait defining actor behavior and lifecycle hooks (on_start required, on_run optional).
  • Message<M>: Trait for handling a message type M and defining its reply type.
  • ActorRef: Handle for sending messages to an actor.
  • spawn: Function to create and start an actor, returning an ActorRef and a JoinHandle.
  • ActorResult: Enum representing the outcome of an actor’s lifecycle (e.g., completed, failed).

§Getting Started

§Message Handling with #[message_handlers]

rsActor uses the #[message_handlers] attribute macro combined with #[handler] method attributes for message handling. This is required for all actors and offers several advantages:

  • Selective Processing: Only methods marked with #[handler] are treated as message handlers.
  • Clean Separation: Regular methods can coexist with message handlers within the same impl block.
  • Automatic Generation: The macro automatically generates the necessary Message trait implementations and handler registrations.
  • Type Safety: Message handler signatures are verified at compile time.
  • Reduced Boilerplate: Eliminates the need to manually implement Message traits.

§Option A: Simple Actor with #[derive(Actor)]

For simple actors that don’t need complex initialization logic, use the #[derive(Actor)] macro:

use rsactor::{Actor, ActorRef, message_handlers, spawn};

// 1. Define your actor struct and derive Actor
#[derive(Actor)]
struct MyActor {
    name: String,
    count: u32,
}

// 2. Define message types
struct GetName;
struct Increment;

// 3. Use message_handlers macro with handler attributes
#[message_handlers]
impl MyActor {
    #[handler]
    async fn handle_get_name(&mut self, _msg: GetName, _: &ActorRef<Self>) -> String {
        self.name.clone()
    }

    #[handler]
    async fn handle_increment(&mut self, _msg: Increment, _: &ActorRef<Self>) -> () {
        self.count += 1;
    }

    // Regular methods can coexist without the #[handler] attribute
    fn get_count(&self) -> u32 {
        self.count
    }
}

// 4. Usage
let actor_instance = MyActor { name: "Test".to_string(), count: 0 };
let (actor_ref, _join_handle) = spawn::<MyActor>(actor_instance);

let name = actor_ref.ask(GetName).await?;
actor_ref.tell(Increment).await?;

§Option B: Custom Actor Implementation with Manual Initialization

For actors that need custom initialization logic, implement the Actor trait manually:

use rsactor::{Actor, ActorRef, message_handlers, spawn};
use anyhow::Result;

// 1. Define your actor struct
#[derive(Debug)] // Added Debug for printing the actor in ActorResult
struct MyActor {
    data: String,
    count: u32,
}

// 2. Implement the Actor trait manually
impl Actor for MyActor {
    type Args = String;
    type Error = anyhow::Error;

    // on_start is required and must be implemented.
    // on_run and on_stop are optional and have default implementations.
    async fn on_start(initial_data: Self::Args, actor_ref: &ActorRef<Self>) -> std::result::Result<Self, Self::Error> {
        println!("MyActor (id: {}) started with data: '{}'", actor_ref.identity(), initial_data);
        Ok(MyActor {
            data: initial_data,
            count: 0,
        })
    }
}

// 3. Define message types
struct GetData;
struct IncrementMsg(u32);

// 4. Use message_handlers macro for message handling
#[message_handlers]
impl MyActor {
    #[handler]
    async fn handle_get_data(&mut self, _msg: GetData, _actor_ref: &ActorRef<Self>) -> String {
        self.data.clone()
    }

    #[handler]
    async fn handle_increment(&mut self, msg: IncrementMsg, _actor_ref: &ActorRef<Self>) -> u32 {
        self.count += msg.0;
        self.count
    }
}

// 5. Usage
let (actor_ref, join_handle) = spawn::<MyActor>("initial data".to_string());

let current_data: String = actor_ref.ask(GetData).await?;
let new_count: u32 = actor_ref.ask(IncrementMsg(5)).await?;

actor_ref.stop().await?;
let actor_result = join_handle.await?;

Both approaches also work with enums, making it easy to create state machine actors:

use rsactor::{Actor, ActorRef, message_handlers, spawn};

// Using message_handlers macro approach
#[derive(Actor, Clone)]
enum StateActor {
    Idle,
    Processing(String),
    Completed(i32),
}

struct GetState;
struct StartProcessing(String);
struct Complete(i32);

#[message_handlers]
impl StateActor {
    #[handler]
    async fn handle_get_state(&mut self, _msg: GetState, _: &ActorRef<Self>) -> StateActor {
        self.clone()
    }

    #[handler]
    async fn handle_start_processing(&mut self, msg: StartProcessing, _: &ActorRef<Self>) -> () {
        *self = StateActor::Processing(msg.0);
    }

    #[handler]
    async fn handle_complete(&mut self, msg: Complete, _: &ActorRef<Self>) -> () {
        *self = StateActor::Completed(msg.0);
    }
}

§Tracing Support

rsActor provides optional tracing support for comprehensive observability. Enable it with the tracing feature:

[dependencies]
rsactor = { version = "0.9", features = ["tracing"] }
tracing = "0.1"
tracing-subscriber = "0.3"

When enabled, rsActor emits structured trace events for:

  • Actor lifecycle events (start, stop, termination scenarios)
  • Message sending and handling with timing information
  • Reply processing and error handling
  • Performance metrics (message processing duration)

All examples support tracing with conditional compilation. Here’s the integration pattern:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracing if the feature is enabled
    #[cfg(feature = "tracing")]
    {
        tracing_subscriber::fmt()
            .with_max_level(tracing::Level::DEBUG)
            .with_target(false)
            .init();
        println!("🚀 Demo: Tracing is ENABLED");
    }

    #[cfg(not(feature = "tracing"))]
    {
        env_logger::init();
        println!("📝 Demo: Tracing is DISABLED");
    }

    // Your existing actor code here...
    // When tracing is enabled, you'll see detailed logs automatically
    Ok(())
}

Run any example with tracing enabled:

RUST_LOG=debug cargo run --example basic --features tracing

This crate-level documentation provides an overview of rsActor. For more details on specific components, please refer to their individual documentation.

Structs§

ActorRef
A type-safe reference to an actor of type T.
ActorWeak
A weak, type-safe reference to an actor of type T.
Identity

Enums§

ActorResult
Result type returned when an actor’s lifecycle completes.
Error
Represents errors that can occur in the rsactor framework.
FailurePhase
Represents the phase during which an actor failure occurred.

Constants§

DEFAULT_MAILBOX_CAPACITY
The default mailbox capacity for actors.

Traits§

Actor
Defines the behavior of an actor.
Message
A trait for messages that an actor can handle, defining the reply type.

Functions§

set_default_mailbox_capacity
Sets the global default buffer size for actor mailboxes.
spawn
Spawns a new actor and returns an ActorRef<T> to it, along with a JoinHandle.
spawn_with_mailbox_capacity
Spawns a new actor with a specified mailbox capacity and returns an ActorRef<T> to it, along with a JoinHandle.

Type Aliases§

Result
A Result type specialized for rsactor operations.

Attribute Macros§

message_handlers
Attribute macro for automatically generating Message trait implementations from method definitions.

Derive Macros§

Actor
Derive macro for automatically implementing the Actor trait.