# acktor
[](https://crates.io/crates/acktor)
[](https://docs.rs/acktor)
[](https://github.com/asymmetry/acktor/actions/workflows/ci.yml)
[](https://codecov.io/gh/asymmetry/acktor)
[](LICENSE)
A pure-Rust actor framework built on top of the [Tokio](https://tokio.rs) async runtime, inspired by Alice Ryhl's [Actors with Tokio](https://ryhl.io/blog/actors-with-tokio/).
## About
`acktor` is an actor framework for Rust that builds on the patterns described in Alice Ryhl's blog post and extends them into a structured library. Each actor runs as an independent `tokio` task with its own mailbox, processing messages one at a time. Actors communicate exclusively through message passing — there is no shared mutable state. The framework provides lifecycle hooks, supervision, an observer pattern, and support for periodic tasks.
## Installation
Install `acktor` by adding it to your `Cargo.toml`:
```toml
[dependencies]
acktor = "1.0"
```
Requires Rust 1.88 or later.
## Quick Start
An example `Counter` actor that handles arithmetic messages might be the following:
```rust
use acktor::{Actor, Context, Handler, Message, Signal};
// 1. Define your actor
#[derive(Debug)]
struct Counter(i64);
impl Actor for Counter {
type Context = Context<Self>;
type Error = String;
}
// 2. Define a message
#[derive(Debug, Message)]
#[result_type(i64)]
enum CounterMsg {
Increment,
Get,
}
// 3. Implement the handler
impl Handler<CounterMsg> for Counter {
type Result = i64;
async fn handle(&mut self, msg: CounterMsg, _ctx: &mut Self::Context) -> i64 {
match msg {
CounterMsg::Increment => self.0 += 1,
CounterMsg::Get => {}
}
self.0
}
}
#[tokio::main]
async fn main() {
let (addr, handle) = Counter(0).start("counter").unwrap();
// fire-and-forget
addr.do_send(CounterMsg::Increment).await.unwrap();
// request-reply
let result = addr.send(CounterMsg::Get).await.unwrap().await.unwrap();
println!("Counter: {}", result); // Counter: 1
addr.do_send(Signal::Stop).await.unwrap();
handle.await.unwrap();
}
```
## Supervision
Implement `Handler<SupervisionEvent<A>>` on the supervisor actor. Use the command `Supervisor::Set` to attach the supervisor actor to the child (or `Supervisor::Unset` to detach). Since every actor handles `Supervisor<A>` automatically, no extra wiring is needed on the child side.
```rust
use acktor::{Actor, Context, Handler, supervisor::SupervisionEvent};
struct Worker;
impl Actor for Worker {
type Context = Context<Self>;
type Error = String;
}
#[derive(Default)]
struct Watchdog;
impl Actor for Watchdog {
type Context = Context<Self>;
type Error = String;
}
impl Handler<SupervisionEvent<Worker>> for Watchdog {
type Result = ();
async fn handle(&mut self, event: SupervisionEvent<Worker>, _ctx: &mut Self::Context) {
println!("worker event: {:?}", event);
}
}
```
## Observer
For a subject actor, implement the `SubjectActor<Event>` trait so it can emit `Event`s to registered observers. Every subject actor automatically gets a `Handler<Observer<Event>>` implementation that manages the observers, so the observers can be registered by sending `Observer::Register` commands to the subject actor (or `Observer::Unregister` to stop receiving events).
```rust
use acktor::{Actor, Context, Message, observer::{ObserverSet, SubjectActor}};
#[derive(Clone, Message)]
#[result_type(())]
struct Tick;
#[derive(Default)]
struct Clock { observers: ObserverSet<Tick> }
impl Actor for Clock {
type Context = Context<Self>;
type Error = String;
}
impl SubjectActor<Tick> for Clock {
fn observers_mut(&mut self) -> &mut ObserverSet<Tick> { &mut self.observers }
}
```
## Cron Tasks
Use `CronContext<Self>` as the actor's context and implement `CronActor` trait to opt in this feature. `CronActor` trait defines a `task` method that is invoked repeatedly with a delay determined by its return value.
```rust
use std::time::Duration;
use acktor::{Actor, cron::{CronActor, CronContext}};
struct Heartbeat;
impl Actor for Heartbeat {
type Context = CronContext<Self>;
type Error = String;
}
impl CronActor for Heartbeat {
async fn task(&mut self, _ctx: &mut Self::Context) -> Result<Duration, Self::Error> {
println!("tick");
Ok(Duration::from_secs(1))
}
}
```
## IPC Support
Actors in different processes can talk to each other through the [`acktor-ipc`](./acktor-ipc) crate. Enable the `ipc` feature on `acktor`, add `acktor-ipc` to your dependencies, mark the actors you want to expose with `#[derive(RemoteAddressable)]` + `#[remote]`, and connect them through a `Node` over a `pipe` or `websocket` transport. See the [`acktor-ipc` README](./acktor-ipc/README.md) and the [`pingpong` example](./acktor-ipc/examples/pingpong) for a full walkthrough.
## Feature Flags
Defaults: `derive`, `observer`, `cron`.
| `derive` | Re-exports the derive macros from `acktor-derive`. |
| `observer` | Enables the observer module. |
| `cron` | Enables the cron module. |
| `identifier` | Enables stable type identifiers. |
| `ipc` | Enables IPC support (codec module, remote addressing). |
| `prost-codec` | Use an all-prost primitive codec instead of the zerocopy mix. |
| `bottleneck-warning` | Logs when an observer's mailbox is full. |
| `tokio-tracing` | Names actor tasks for `tokio-console` (needs `tokio_unstable`). |
## License
This project is licensed under [MIT](LICENSE).