rsactor 0.2.0

A Simplified Actor Framework for Rust
Documentation
# rsActor: A Simplified Actor Framework for Rust

`rsActor` is a lightweight, Tokio-based actor framework in Rust, inspired by [Kameo](https://github.com/tqwewe/kameo). It prioritizes simplicity and ease of use for local, in-process actor systems by stripping away more complex features like remote actors and supervision trees.

**Note:** This project is in a very early stage of development. APIs are subject to change, and features are still being actively developed.

## Core Features

*   **Minimalist Actor System**: Focuses on the core actor model primitives.
*   **Asynchronous Message Passing**:
    *   `ask`: Send a message and asynchronously await a reply.
    *   `tell`: Send a message without waiting for a reply (fire-and-forget).
*   **Actor Lifecycle**: Actors implement `on_start` and `on_stop` hooks.
*   **Graceful & Immediate Termination**: Actors can be stopped gracefully (processing remaining messages) or killed immediately.
*   **Macro-Assisted Message Handling**: The `impl_message_handler!` macro simplifies routing different message types to their respective handlers within an actor.
*   **Tokio-Native**: Built exclusively for the `tokio` asynchronous runtime.

## Comparison with Kameo

While `rsActor` shares the goal of providing an actor system, it makes different design choices compared to Kameo:

*   **No Remote Actor Support**: `rsActor` is for local actors only.
*   **Non-Generic `ActorRef`**: `rsActor`'s `ActorRef` is a concrete type. Messages are dynamically typed (`Box<dyn Any + Send>`), with runtime type checking for replies in `ask` calls. Kameo uses a generic `ActorRef<A: Actor>`.
*   **No Actor Linking or Supervision**: `rsActor` does not include built-in support for linking actor lifecycles or supervision strategies.
*   **Tokio-Specific**: `rsActor` is tightly coupled with Tokio. Kameo is designed for broader async runtime compatibility.
*   **`impl_message_handler!` Macro**: `rsActor` uses a macro to generate the boilerplate for handling multiple message types, whereas Kameo might use generic trait implementations per message.

## Getting Started

### 1. Add Dependency

Add `rsActor` to your `Cargo.toml`:

```toml
[dependencies]
rsactor = "0.2"
```

### 2. Basic Usage Example

Here's a simple counter actor:

```rust
use rsactor::{Actor, ActorRef, Message, ActorStopReason, impl_message_handler, spawn};
use anyhow::Result;
use log::info;

// Define your actor struct
struct CounterActor {
    count: u32,
}

// Implement the Actor trait
impl Actor for CounterActor {
    type Error = anyhow::Error;

    async fn on_start(&mut self,
        actor_ref: ActorRef
    ) -> Result<(), Self::Error> {
        info!("CounterActor (id: {}) started. Initial count: {}", actor_ref.id(), self.count);
        Ok(())
    }

    async fn on_stop(&mut self,
        actor_ref: ActorRef,
        stop_reason: &ActorStopReason
    ) -> Result<(), Self::Error> {
        info!("CounterActor (id: {}) stopping. Final count: {}. Reason: {:?}", actor_ref.id(), self.count, stop_reason);
        Ok(())
    }
}

// Define message types
struct IncrementMsg(u32); // Message to increment the counter by a value
struct GetCountMsg;       // Message to get the current count

// Implement Message<T> for CounterActor to handle IncrementMsg
impl Message<IncrementMsg> for CounterActor {
    type Reply = u32; // This message expects a u32 reply (the new count)

    async fn handle(&mut self, msg: IncrementMsg) -> Self::Reply {
        self.count += msg.0;
        self.count // Return the new count
    }
}

// Implement Message<T> for CounterActor to handle GetCountMsg
impl Message<GetCountMsg> for CounterActor {
    type Reply = u32; // This message expects a u32 reply (the current count)

    async fn handle(&mut self, _msg: GetCountMsg) -> Self::Reply {
        self.count // Return the current count
    }
}

// Use the impl_message_handler! macro to generate boilerplate
// for routing IncrementMsg and GetCountMsg to their respective handlers.
impl_message_handler!(CounterActor, [IncrementMsg, GetCountMsg]);

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize the logger (e.g., env_logger) to see log messages.
    // Ensure you have `env_logger` and `log` in your Cargo.toml.
    env_logger::init();

    let initial_count = 0u32;
    let counter_actor_instance = CounterActor { count: initial_count };
    info!("Creating CounterActor with initial count: {}", initial_count);

    // Spawn the actor.
    // spawn returns a tuple:
    // 1. ActorRef: A handle to send messages to the actor.
    // 2. JoinHandle: A handle to await the actor's termination and retrieve its final state.
    info!("Spawning CounterActor...");
    let (actor_ref, join_handle) = spawn(counter_actor_instance);
    info!("CounterActor spawned with ID: {}", actor_ref.id());

    // Send an IncrementMsg using 'ask' to get a reply.
    let increment_value = 5u32;
    info!("Sending IncrementMsg({}) to CounterActor (ID: {})...", increment_value, actor_ref.id());
    let count_after_increment: u32 = actor_ref.ask(IncrementMsg(increment_value)).await?;
    info!("Received reply after increment: new count = {}", count_after_increment);

    // Send a GetCountMsg using 'ask'.
    info!("Sending GetCountMsg to CounterActor (ID: {})...", actor_ref.id());
    let current_count: u32 = actor_ref.ask(GetCountMsg).await?;
    info!("Received reply for GetCountMsg: current count = {}", current_count);

    // Stop the actor gracefully.
    // The actor will process all messages currently in its mailbox before stopping.
    // The on_stop hook will be called.
    info!("Sending stop signal to CounterActor (ID: {})...", actor_ref.id());
    actor_ref.stop().await?;
    info!("Stop signal sent. CounterActor (ID: {}) will shut down gracefully.", actor_ref.id());

    // Wait for the actor's task to complete.
    // This is important to ensure the actor has fully stopped and resources are cleaned up.
    // join_handle.await returns a Result containing the actor's final state and stop reason.
    info!("Waiting for CounterActor (ID: {}) to complete its task...", actor_ref.id());
    let (stopped_actor, reason) = join_handle.await?;
    info!(
        "CounterActor (ID: {}) task completed. Final count: {}. Stop reason: {:?}",
        actor_ref.id(), // Note: actor_ref.id() is still usable here
        stopped_actor.count,
        reason
    );

    info!("Example finished.");
    Ok(())
}
```

## Running the Example

The project includes a basic example in `examples/basic.rs`. You can run it using:

```bash
cargo run --example basic
```

This will demonstrate actor creation, message passing, and lifecycle logging.

## Motivation

The primary goal of this project is to provide a streamlined and efficient actor-based framework by focusing on core functionalities while reducing complexity. This makes it suitable for scenarios where a full-featured actor system like `actix` might be overkill, but the actor model's benefits (concurrency, state encapsulation) are still desired.

## License

This project is licensed under the **Apache License 2.0**. You can find a copy of the license in the LICENSE-APACHE file.

## Contribution

Contributions are welcome! Feel free to open issues and submit pull requests to improve the project.