zestors is an actor framework for Rust with Erlang/OTP-style supervision.
An actor is a just a tokio task that owns an Inbox and processes messages.
Messages are defined as plain structs deriving Message, and grouped into an
Interface that defines the messages an Actor accepts. Actors can also be supervised: a Supervisor starts, watches, and restarts a set of children according to its restart strategy. A Node then runs a root supervisor as an entire program, shutting it down gracefully on Ctrl+C/SIGTERM.
This repository is a Cargo workspace; most consumers should depend on the
zestors facade crate, which re-exports the other crates
as modules.
Zestors was written from the ground up with a couple of core ideas in mind, that guided it's evolution
Handle for all accepted messages, more complex actors will want to write their own event-loops for more control.Actor and Blueprint traits entirely!Interface, allowing e.g. Vec<Address<Dyn(Gethealth, GetChildren)>> to be made up of different concrete addresses, all strongly-typed. (See type_sets for more details)A perfect example of what this enables, is the GetHealth and GetChildren messages defined in zestors-supervision. These messages are just normal messages, but allow for building a robust, observable supervision-tree that can be inspected at runtime through zestors-api-server. Build a custom supervisor? Just make sure its interface contains the message GetChildren, and it is automatically wired up in the tree.
A worker actor, written with Handler, supervised by a Supervisor, run as
a program with Node:
use zestors::interface::{Envelope, Interface, Message};
use zestors::prelude::*;
use zestors::supervisor::{Node, Supervisor};
#[derive(Message, Debug)]
struct Ping;
#[derive(Interface, HandlerInterface, Debug)]
enum WorkerInterface {
Ping(Envelope<Ping>),
}
#[derive(Debug, Clone)]
struct Worker;
impl Handler for Worker {
type Interface = WorkerInterface;
}
impl Handle<Ping> for Worker {
async fn handle(
&mut self,
_ctx: HandlerContext<'_, Self>,
_msg: Ping,
_req: (),
) -> Result<(), rootcause::Report> {
Ok(())
}
}
#[tokio::main]
async fn main() {
let node = Node::new(
Supervisor::blueprint()
.child(Worker.pid("worker").unwrap())
.rand_pid(),
);
// Starts the supervisor, restarts it on error, and shuts it down
// gracefully on Ctrl+C/SIGTERM.
node.run().await.unwrap();
}
The zestors crate docs are the main
documentation: they walk through defining messages, spawning actors,
sending and receiving, and building supervision trees, with runnable
examples for each step. Each workspace crate also documents the layer it
provides — see the crate list below.
There is a WIP inspector built using egui. It is still very much a proof-of-concept, but can already be used to inspect a running system.

| Crate | What it provides |
|---|---|
zestors | Facade crate re-exporting the others; start here. |
zestors-interface | Message/Interface: the vocabulary for defining what an actor accepts. |
zestors-runtime | The actor runtime: Inbox, Address, Pid, Registry, signals. |
zestors-actor | Handler/Actor: declarative and low-level ways to implement an actor. |
zestors-supervision | ChildSpec/ChildConfig/RestartIntensity: the supervisor's vocabulary. |
zestors-supervisor | The Supervisor and Node actors that use it. |
zestors-api-server | HTTP introspection for a running actor tree. |
zestors-codegen | The #[derive(Message)]/#[derive(Interface)] proc macros. |
zestors-inspector | A GUI that visualizes the tree data zestors-api-server exposes (not re-exported by zestors). |
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
369 commits
Rust
99.8%
zestors is an actor framework for Rust with Erlang/OTP-style supervision.
An actor is a just a tokio task that owns an Inbox and processes messages.
Messages are defined as plain structs deriving Message, and grouped into an
Interface that defines the messages an Actor accepts. Actors can also be supervised: a Supervisor starts, watches, and restarts a set of children according to its restart strategy. A Node then runs a root supervisor as an entire program, shutting it down gracefully on Ctrl+C/SIGTERM.
This repository is a Cargo workspace; most consumers should depend on the
zestors facade crate, which re-exports the other crates
as modules.
Zestors was written from the ground up with a couple of core ideas in mind, that guided it's evolution
Handle for all accepted messages, more complex actors will want to write their own event-loops for more control.Actor and Blueprint traits entirely!Interface, allowing e.g. Vec<Address<Dyn(Gethealth, GetChildren)>> to be made up of different concrete addresses, all strongly-typed. (See type_sets for more details)A perfect example of what this enables, is the GetHealth and GetChildren messages defined in zestors-supervision. These messages are just normal messages, but allow for building a robust, observable supervision-tree that can be inspected at runtime through zestors-api-server. Build a custom supervisor? Just make sure its interface contains the message GetChildren, and it is automatically wired up in the tree.
A worker actor, written with Handler, supervised by a Supervisor, run as
a program with Node:
use zestors::interface::{Envelope, Interface, Message};
use zestors::prelude::*;
use zestors::supervisor::{Node, Supervisor};
#[derive(Message, Debug)]
struct Ping;
#[derive(Interface, HandlerInterface, Debug)]
enum WorkerInterface {
Ping(Envelope<Ping>),
}
#[derive(Debug, Clone)]
struct Worker;
impl Handler for Worker {
type Interface = WorkerInterface;
}
impl Handle<Ping> for Worker {
async fn handle(
&mut self,
_ctx: HandlerContext<'_, Self>,
_msg: Ping,
_req: (),
) -> Result<(), rootcause::Report> {
Ok(())
}
}
#[tokio::main]
async fn main() {
let node = Node::new(
Supervisor::blueprint()
.child(Worker.pid("worker").unwrap())
.rand_pid(),
);
// Starts the supervisor, restarts it on error, and shuts it down
// gracefully on Ctrl+C/SIGTERM.
node.run().await.unwrap();
}
The zestors crate docs are the main
documentation: they walk through defining messages, spawning actors,
sending and receiving, and building supervision trees, with runnable
examples for each step. Each workspace crate also documents the layer it
provides — see the crate list below.
There is a WIP inspector built using egui. It is still very much a proof-of-concept, but can already be used to inspect a running system.

| Crate | What it provides |
|---|---|
zestors | Facade crate re-exporting the others; start here. |
zestors-interface | Message/Interface: the vocabulary for defining what an actor accepts. |
zestors-runtime | The actor runtime: Inbox, Address, Pid, Registry, signals. |
zestors-actor | Handler/Actor: declarative and low-level ways to implement an actor. |
zestors-supervision | ChildSpec/ChildConfig/RestartIntensity: the supervisor's vocabulary. |
zestors-supervisor | The Supervisor and Node actors that use it. |
zestors-api-server | HTTP introspection for a running actor tree. |
zestors-codegen | The #[derive(Message)]/#[derive(Interface)] proc macros. |
zestors-inspector | A GUI that visualizes the tree data zestors-api-server exposes (not re-exported by zestors). |
Licensed under either of Apache License, Version 2.0 or MIT license at your option.
369 commits
Rust
99.8%