Introduction - Murmer — A Distributed Actor Framework for Rust
Keyboard shortcuts
Press ← or → to navigate between chapters
Press S or / to search in the book
Press ? to show this help
Press Esc to hide this help
Auto
Light
Rust
Coal
Navy
Ayu
Murmer — A Distributed Actor Framework for Rust
Introduction
Murmer is a distributed actor framework for Rust, built on tokio and QUIC.
It provides typed, location-transparent actors that communicate through message passing. Whether an actor lives in the same process or on a remote node across the network, you interact with it through the same Endpoint API.
Why I built this<br>I’ve spent years working with Elixir and the BEAM VM, and the actor model there is something I’ve grown deeply fond of — the simplicity of processes, message passing, and supervision just works. When I looked at bringing that experience to Rust, I studied existing implementations like Actix, Telepathy, and Akka (on the JVM side). They’re impressive systems, but I kept running into the same friction: getting a basic actor up and running was complex, and adding remote communication across nodes was even more so.
Murmer is an experiment in answering a simple question: can you build a robust distributed actor system in Rust that’s actually simple to use?
The answer, it seems, is yes.
The design draws heavy inspiration from BEAM OTP’s supervision and process model, Akka’s clustering approach, and Apple’s Swift Distributed Actors for the typed, location-transparent endpoint API. The goal is to combine these ideas with Rust’s performance and safety guarantees — zero-cost local dispatch, compile-time message type checking, and automatic serialization over encrypted QUIC connections when actors span nodes.
Murmer in 1 minute
Install:
[dependencies]<br>murmer = "0.3"<br>serde = { version = "1", features = ["derive"] }<br>tokio = { version = "1", features = ["full"] }
Write an actor, send it messages:
use murmer::prelude::*;
// ① Define your actor — state lives separately<br>#[derive(Debug)]<br>struct Counter;<br>struct CounterState { count: i64 }
impl Actor for Counter {<br>type State = CounterState;
// ② Handlers become the actor's API<br>#[handlers]<br>impl Counter {<br>#[handler]<br>fn increment(<br>&mut self,<br>_ctx: &ActorContext,<br>state: &mut CounterState,<br>amount: i64,<br>) -> i64 {<br>state.count += amount;<br>state.count
#[handler]<br>fn get_count(<br>&mut self,<br>_ctx: &ActorContext,<br>state: &mut CounterState,<br>) -> i64 {<br>state.count
#[tokio::main]<br>async fn main() {<br>// ③ Create a local actor system<br>let system = System::local();
// ④ Start an actor — returns a typed Endpoint<br>let counter = system.start("counter/main", Counter, CounterState { count: 0 });
// ⑤ Send messages via auto-generated extension methods<br>let result = counter.increment(5).await.unwrap();<br>println!("Count: {result}"); // → Count: 5
// ⑥ Look up actors by label — works for local and remote<br>let found = system.lookup::("counter/main").unwrap();<br>let count = found.get_count().await.unwrap();<br>println!("Looked up: {count}"); // → Looked up: 5<br>cargo run
That’s it — a complete, working actor system. The rest of this page explains what’s happening under the hood. The Getting Started chapter goes deeper into each component.
What it gives you
Send messages without caring where the actor lives. counter.increment(5) (line ⑤) works identically whether the actor is local or on a remote node — the Endpoint API abstracts the difference away.
Test distributed systems from a single process. System::local() (line ③) runs everything in-memory. Swap to System::clustered() when you’re ready for real networking — your actor code stays identical.
Define actors with minimal boilerplate. The #[handlers] macro (line ②) auto-generates message structs (Increment, GetCount), dispatch tables, serialization, and the extension methods you call on line ⑤.
Get networking and encryption handled for you. QUIC transport with automatic TLS, SWIM-based cluster membership, and mDNS discovery — all configured, not hand-rolled.
Supervise actors like OTP. Restart policies (Temporary, Transient, Permanent) with configurable limits and exponential backoff keep your system running through failures.
Orchestrate applications across a cluster. The app module adds placement strategies, leader election, and crash recovery — so you can declare what should run and where, and the framework handles the rest.
What’s happening: line by line
① Actor + State — Counter is a zero-sized struct. All mutable state lives in CounterState, passed as &mut to every handler. This keeps the actor lightweight and the state threading explicit.
② #[handlers] — The macro reads your method signatures and generates:
Message structs — Increment { pub amount: i64 } and GetCount (unit struct)
Handler and Handler trait implementations
RemoteDispatch — a wire-format dispatch table so remote nodes know how to route messages to the right handler
CounterExt — an extension trait on Endpoint that gives you .increment(amount) and...