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.
tell
: Send a message without waiting for a reply (fire-and-forget).tell_with_timeout
: Send a message without waiting for a reply, with a specified timeout.ask
: Send a message and await a reply.ask_with_timeout
: Send a message and await a reply, with a specified timeout.tell_blocking
: Blocking version oftell
for use intokio::task::spawn_blocking
tasks.ask_blocking
: Blocking version ofask
for use intokio::task::spawn_blocking
tasks.
- Straightforward Actor Lifecycle: Actors have
on_start
,on_run
, andon_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 withtracing
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 typeM
and defining its reply type.ActorRef
: Handle for sending messages to an actor.spawn
: Function to create and start an actor, returning anActorRef
and aJoinHandle
.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§
- Actor
Ref - A type-safe reference to an actor of type
T
. - Actor
Weak - A weak, type-safe reference to an actor of type
T
. - Identity
Enums§
- Actor
Result - Result type returned when an actor’s lifecycle completes.
- Error
- Represents errors that can occur in the rsactor framework.
- Failure
Phase - 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 aJoinHandle
. - spawn_
with_ mailbox_ capacity - Spawns a new actor with a specified mailbox capacity and returns an
ActorRef<T>
to it, along with aJoinHandle
.
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.