Crate rsactor

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
  • Dead Letter Tracking: Automatic logging of undelivered messages via DeadLetterReason:
    • All failed message deliveries are logged with actor and message type information
    • Helps identify stopped actors, timeouts, and dropped replies
    • Zero overhead on successful message delivery (hot path optimization)
  • Enhanced Error Debugging: Rich error information via Error::debugging_tips() and Error::is_retryable():
    • Actionable debugging tips for each error type
    • Retry classification for timeout errors

§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. Here’s the integration pattern:

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize tracing subscriber to see logs
    // The `tracing` crate is always available for logging
    tracing_subscriber::fmt()
        .with_max_level(tracing::Level::DEBUG)
        .with_target(false)
        .init();

    // Your existing actor code here...
    // Logs are automatically emitted via tracing::warn!, tracing::error!, etc.
    Ok(())
}

Run any example with debug logging:

RUST_LOG=debug cargo run --example basic

Enable instrumentation spans with the tracing feature:

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.
DeadLetterReason
Reason why a message became a dead letter.
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.
ActorControl
Type-erased trait for actor lifecycle control with strong references.
AskHandler
Request-response message handler for strong references (object-safe).
Message
A trait for messages that an actor can handle, defining the reply type.
TellHandler
Fire-and-forget message handler for strong references (object-safe).
WeakActorControl
Type-erased trait for actor lifecycle control with weak references.
WeakAskHandler
Weak handler for request-response messages (object-safe).
WeakTellHandler
Weak handler for fire-and-forget messages (object-safe).

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§

BoxFuture
A boxed future that is Send and can be stored in collections.
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.