Crates.io | rsactor |
lib.rs | rsactor |
version | 0.10.0 |
created_at | 2025-05-13 13:29:09.571595+00 |
updated_at | 2025-09-25 12:39:23.902122+00 |
description | A Simple and Efficient In-Process Actor Model Implementation for Rust. |
homepage | https://github.com/hiking90/rsactor |
repository | https://github.com/hiking90/rsactor |
max_upload_size | |
id | 1671921 |
size | 1,862,540 |
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.
Note: This project is actively evolving. While core APIs are stable, some features may be refined in future releases.
#[derive(Actor)]
for simple actors that don't need complex initialization.ask
/ask_with_timeout
: Send a message and asynchronously await a reply.tell
/tell_with_timeout
: Send a message without waiting for a reply.ask_blocking
/tell_blocking
: Blocking versions for tokio::task::spawn_blocking
contexts.on_start
, on_run
, and on_stop
hooks for managing actor behavior:
on_start
: async fn on_start(args: Self::Args, actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error>
- Initializes the actor's state. This method is required.on_run
: async fn on_run(&mut self, actor_ref: &ActorWeak<Self>) -> Result<(), Self::Error>
- Contains the actor's main execution logic, which runs concurrently with message handling. This method is optional and has a default implementation.on_stop
: async fn on_stop(&mut self, actor_ref: &ActorWeak<Self>, killed: bool) -> Result<(), Self::Error>
- Performs cleanup before the actor terminates. The killed
flag indicates whether the termination was graceful (false
) or immediate (true
). This method is optional and has a default implementation.ActorResult
: Enum representing the outcome of an actor's lifecycle (e.g., completed, failed).#[message_handlers]
attribute macro with #[handler]
method attributes for automatic message handlingtokio
asynchronous runtime.ActorRef<T>
) type safety, ensuring message handling consistency and preventing type-related runtime errors.Send
Trait Required: Actor structs only need to implement the Send
trait (not Sync
), enabling the use of interior mutability types like std::cell::Cell
for internal state management without synchronization overhead. The Actor
trait and MessageHandler
trait (via #[message_handlers]
macro) are also required, but they don't add any additional constraints on the actor's fields.tracing
crate. When enabled via the tracing
feature flag, provides comprehensive logging of actor lifecycle events, message handling, and performance metrics.[dependencies]
rsactor = "0.9" # Check crates.io for the latest version
# Optional: Enable tracing support for detailed observability
# rsactor = { version = "0.9", features = ["tracing"] }
For using the derive macros, you'll also need the message_handlers
attribute macro which is included by default.
#[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:
#[handler]
are treated as message handlers.impl
block.Message
trait implementations and handler registrations.Message
traits.#[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 message types
struct Increment;
struct GetCount;
// 2. Define your actor struct and derive Actor
#[derive(Actor)]
struct CounterActor {
count: u32,
}
// 3. Use the #[message_handlers] macro with #[handler] attributes to automatically generate Message trait implementations
#[message_handlers]
impl CounterActor {
#[handler]
async fn handle_increment(&mut self, _msg: Increment, _: &ActorRef<Self>) -> () {
self.count += 1;
}
#[handler]
async fn handle_get_count(&mut self, _msg: GetCount, _: &ActorRef<Self>) -> u32 {
self.count
}
}
// 4. Usage
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let actor = CounterActor { count: 0 };
let (actor_ref, _join_handle) = spawn::<CounterActor>(actor);
actor_ref.tell(Increment).await?;
let count = actor_ref.ask(GetCount).await?;
println!("Count: {}", count); // Prints: Count: 1
actor_ref.stop().await?;
Ok(())
}
For actors that need custom initialization logic, implement the Actor
trait manually:
use rsactor::{Actor, ActorRef, message_handlers, spawn};
use anyhow::Result;
use log::info;
// Define actor struct
#[derive(Debug)] // Added Debug for printing the actor in ActorResult
struct CounterActor {
count: u32,
}
// Implement Actor trait
impl Actor for CounterActor {
type Args = u32; // Define an args type for actor creation
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_count: Self::Args, actor_ref: &ActorRef<Self>) -> Result<Self, Self::Error> {
info!("CounterActor (id: {}) started. Initial count: {}", actor_ref.identity(), initial_count);
Ok(CounterActor {
count: initial_count,
})
}
}
// Define message types
struct IncrementMsg(u32);
// Use message_handlers macro for message handling
#[message_handlers]
impl CounterActor {
#[handler]
async fn handle_increment(&mut self, msg: IncrementMsg, _actor_ref: &ActorRef<Self>) -> u32 {
self.count += msg.0;
self.count
}
}
#[tokio::main]
async fn main() -> Result<()> {
env_logger::init(); // Initialize logger
info!("Creating CounterActor");
let (actor_ref, join_handle) = spawn::<CounterActor>(0u32); // Pass initial count as Args
info!("CounterActor spawned with ID: {}", actor_ref.identity());
let new_count: u32 = actor_ref.ask(IncrementMsg(5)).await?;
info!("Incremented count: {}", new_count);
actor_ref.stop().await?;
info!("Stop signal sent to CounterActor (ID: {})", actor_ref.identity());
let actor_result = join_handle.await?;
info!(
"CounterActor (ID: {}) task completed. Result: {:?}",
actor_ref.identity(),
actor_result
);
Ok(())
}
rsActor comes with several examples that demonstrate various features and use cases:
#[message_handlers]
macro#[message_handlers]
with #[handler]
attributesRun any example with:
cargo run --example <example_name>
All examples support tracing when enabled with the tracing
feature:
RUST_LOG=debug cargo run --example <example_name> --features tracing
rsActor provides optional tracing support for comprehensive observability into actor behavior. When enabled, the framework emits structured trace events for:
To enable tracing support, add the tracing
feature to your dependencies:
[dependencies]
rsactor = { version = "0.9", features = ["tracing"] }
tracing = "0.1"
tracing-subscriber = "0.3"
All examples include tracing support with feature detection. Here's the pattern used:
#[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 actor code here...
Ok(())
}
Run any example with tracing enabled:
RUST_LOG=debug cargo run --example basic --features tracing
For more detailed questions and answers, please see the FAQ.
This project is licensed under the Apache License 2.0. See the LICENSE-APACHE file for details.