An open API service indexing awesome lists of open source software.

https://github.com/hiking90/rsactor

A Simple and Efficient In-Process Actor Model Implementation for Rust.
https://github.com/hiking90/rsactor

actor rust

Last synced: 4 months ago
JSON representation

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

Awesome Lists containing this project

README

          

# rsActor
[![CI](https://github.com/hiking90/rsactor/actions/workflows/rust.yml/badge.svg)](https://github.com/hiking90/rsactor/actions/workflows/rust.yml)
[![Crates.io](https://img.shields.io/crates/v/rsactor.svg)](https://crates.io/crates/rsactor)
[![Docs.rs](https://docs.rs/rsactor/badge.svg)](https://docs.rs/rsactor)
[![Rust Version](https://img.shields.io/badge/rustc-1.75+-blue.svg)](https://blog.rust-lang.org/)

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 and is already being used in production products under active development. While core APIs are stable, some features may be refined in future releases.

## Core Features

### Actor System
- **Minimalist Design**: Focuses on core actor model primitives with a clean API
- **Tokio-Native**: Built for the `tokio` asynchronous runtime
- **Actor Derive Macro**: `#[derive(Actor)]` for simple actors that don't need complex initialization

### Message Passing
| Method | Description |
| -------------------------------- | ------------------------------------------------------------ |
| `ask` / `ask_with_timeout` | Send a message and asynchronously await a reply |
| `tell` / `tell_with_timeout` | Send a message without waiting for a reply (fire-and-forget) |
| `blocking_ask` / `blocking_tell` | Blocking versions for `tokio::task::spawn_blocking` contexts (aliases: `ask_blocking` / `tell_blocking`) |

- **Macro-Assisted Handlers**: `#[message_handlers]` attribute macro with `#[handler]` method attributes for automatic message handling

### Actor Lifecycle
Three well-defined hooks for managing actor behavior:
- `on_start`: Initializes the actor's state (required)
- `on_run`: Runs concurrently with message processing, returns `bool` to control repeated invocation (optional)
- `on_stop`: Cleanup before termination, with `killed` flag for graceful vs immediate (optional)

Supports **graceful termination** (`stop()`) and **immediate termination** (`kill()`), with `ActorResult` enum representing lifecycle outcomes.

### Type Safety
- **Compile-Time Safety**: `ActorRef` ensures message handling consistency and prevents type-related runtime errors
- **Handler Traits**: `TellHandler` and `AskHandler` enable unified management of different actor types in a single collection
- **Actor Control Traits**: `ActorControl` and `WeakActorControl` provide type-erased lifecycle management
- **Only `Send` Required**: Actor structs only need `Send` trait (not `Sync`), enabling interior mutability types like `std::cell::Cell`

### Observability
- **Optional Tracing**: Built-in support via `tracing` feature flag for actor lifecycle events, message handling, and performance metrics
- **Metrics Support**: Optional `metrics` feature for monitoring message counts, processing times, and actor uptime

## Why rsActor?

### Focused Scope
Unlike broader frameworks like Actix, rsActor specializes exclusively in **local, in-process actor systems**. This focused approach eliminates complexity from unused features like remote actors or clustering, resulting in a cleaner API and smaller footprint.

### Key Advantages

- **Simplicity First**: Minimal API surface with sensible defaults
- **Type-Safe by Default**: `ActorRef` ensures compile-time message validation with zero runtime overhead
- **Flexible Type Erasure**: Handler traits enable managing heterogeneous actor collections without sacrificing type safety
- **Production-Ready Observability**: Integrated tracing and metrics support
- **Mutex-Free Design**: No shared locks between actors - state is isolated within each actor

## Getting Started

### 1. Add Dependency

```toml
[dependencies]
rsactor = "0.13" # Check crates.io for the latest version

# Optional: Enable tracing support for detailed observability
# rsactor = { version = "0.13", features = ["tracing"] }
```

For using the derive macros, you'll also need the `message_handlers` attribute macro which is included by default.

### 2. Message Handling with `#[message_handlers]`

rsActor uses the `#[message_handlers]` attribute macro combined with `#[handler]` method attributes for message handling. This is the **recommended** approach 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.

### 3. Choose Your Actor Creation Approach

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

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

```rust
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.count += 1;
}

#[handler]
async fn handle_get_count(&mut self, _msg: GetCount, _: &ActorRef) -> u32 {
self.count
}
}

// 4. Usage
#[tokio::main]
async fn main() -> Result<(), Box> {
let actor = CounterActor { count: 0 };
let (actor_ref, _join_handle) = spawn::(actor);

actor_ref.tell(Increment).await?;
let count = actor_ref.ask(GetCount).await?;
println!("Count: {}", count); // Prints: Count: 1

actor_ref.stop().await?;
Ok(())
}
```

#### Option B: Custom Actor Implementation with Manual Initialization

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

```rust
use rsactor::{Actor, ActorRef, message_handlers, spawn};
use anyhow::Result;
use tracing::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) -> Result {
info!("CounterActor (id: {}) started. Initial count: {}", actor_ref.identity(), initial_count);
Ok(CounterActor {
count: initial_count,
})
}
}

// Define message types
struct Increment(u32);

// Use message_handlers macro for message handling
#[message_handlers]
impl CounterActor {
#[handler]
async fn handle_increment(&mut self, msg: Increment, _actor_ref: &ActorRef) -> u32 {
self.count += msg.0;
self.count
}
}

#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt().init(); // Initialize tracing

info!("Creating CounterActor");

let (actor_ref, join_handle) = spawn::(0u32); // Pass initial count as Args
info!("CounterActor spawned with ID: {}", actor_ref.identity());

let new_count: u32 = actor_ref.ask(Increment(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(())
}
```

## Examples

rsActor comes with several examples that demonstrate various features and use cases:

* **[basic](./examples/basic.rs)** - Simple counter actor demonstrating core concepts with `#[message_handlers]` macro
* **[actor_with_timeout](./examples/actor_with_timeout.rs)** - Using timeouts for actor communication
* **[actor_async_worker](./examples/actor_async_worker.rs)** - Inter-actor communication with async tasks
* **[actor_blocking_task](./examples/actor_blocking_task.rs)** - Using blocking APIs with actors
* **[dining_philosophers](./examples/dining_philosophers.rs)** - Classic concurrency problem implementation
* **[weak_reference_demo](./examples/weak_reference_demo.rs)** - Working with weak actor references and lifecycle
* **[handler_demo](./examples/handler_demo.rs)** - Using handler traits for unified actor management
* **[ask_join_demo](./examples/ask_join_demo.rs)** - Using `ask_join` for CPU/IO-bound operations
* **[metrics_demo](./examples/metrics_demo.rs)** - Actor performance monitoring (requires `metrics` feature)
* **[tracing_demo](./examples/tracing_demo.rs)** - Structured logging and actor lifecycle tracing

Run any example with:
```bash
cargo run --example
```

All examples support tracing when enabled with the `tracing` feature:
```bash
RUST_LOG=debug cargo run --example --features tracing
```

## Optional Features

### Tracing Support

rsActor provides optional tracing support for comprehensive observability into actor behavior. When enabled, the framework 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)

To enable tracing support, add the `tracing` feature to your dependencies:

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

All examples include tracing support. Here's the recommended initialization pattern:

```rust
#[tokio::main]
async fn main() -> Result<(), Box> {
// Initialize tracing subscriber
tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_target(false)
.init();

// Your actor code here...
Ok(())
}
```

Run any example with tracing enabled:
```bash
RUST_LOG=debug cargo run --example basic --features tracing
```

## Handler Traits

Handler traits (`TellHandler`, `AskHandler`, `WeakTellHandler`, `WeakAskHandler`) enable unified management of different actor types handling the same message in a single collection. See the [Handler Traits Documentation](./docs/handler_traits_design.md) for details.

## Actor Control Traits

Actor control traits (`ActorControl`, `WeakActorControl`) provide type-erased lifecycle management for different actor types in a single collection. Handler traits provide `as_control()` and `as_weak_control()` methods to access lifecycle operations.

## Documentation

- **[Getting Started](./docs/getting_started.md)** - Quick start guide
- **[Actor Model](./docs/actor_model.md)** - Actor model concepts
- **[Architecture](./docs/Architecture.md)** - System architecture overview
- **[Best Practices](./docs/best_practices.md)** - Recommended patterns and practices
- **[Debugging Guide](./docs/debugging_guide.md)** - Error handling, dead letter tracking, and troubleshooting
- **[Metrics Guide](./docs/metrics.md)** - Actor performance monitoring
- **[Tracing Guide](./docs/tracing.md)** - Detailed observability with tracing
- **[FAQ](./docs/FAQ.md)** - Common questions and answers

## Contributing

We welcome contributions! Here's how to get started:

### Development Setup

```bash
git clone https://github.com/hiking90/rsactor.git
cd rsactor

# Run tests
cargo test --all-features

# Run examples
cargo run --example basic

# With tracing
RUST_LOG=debug cargo run --example basic --features tracing
```

### Code Quality

Before submitting a PR, ensure:

```bash
cargo fmt # Format code
cargo clippy --all-targets --all-features -- -D warnings # Lint check
cargo test --all-features # All tests pass
```

### Ways to Contribute

- Bug reports and fixes
- Documentation improvements
- New examples
- Performance optimizations
- Feature requests

## Claude Code Skills

rsActor provides [Claude Code](https://claude.ai/code) skills to help AI assistants write correct rsactor code.

### Installation

```bash
# Global installation (recommended)
curl -sSL https://raw.githubusercontent.com/hiking90/rsactor/main/install-skills.sh | bash

# Project-local installation
curl -sSL https://raw.githubusercontent.com/hiking90/rsactor/main/install-skills.sh | bash -s -- --local
```

### Available Skills

- **rsactor-actor**: Create new actors with proper patterns
- **rsactor-handler**: Add message handlers to existing actors
- **rsactor-guide**: API reference and troubleshooting guide

## License

This project is licensed under the Apache License 2.0. See the [LICENSE-APACHE](LICENSE-APACHE) file for details.