Chapter 1 - Actor Model

People often ask me what my favourite actor framework in Rust is. They are often surprised when I tell them tokio!

Quoting Wikipedia:

An actor is a computational entity that, in response to a message it receives, can concurrently:

  • send a finite number of messages to other actors;
  • create a finite number of new actors;
  • designate the behavior to be used for the next message it receives.

There are many actor frameworks for rust:

Looking at kameo, we see


Defining an Actor

use kameo::Actor;
use kameo::message::{Context, Message};

// Implement the actor
#[derive(Actor)]
struct Counter {
    count: i64,
}

// Define message
struct Inc { amount: i64 }

// Implement message handler
impl Message<Inc> for Counter {
    type Reply = i64;

    async fn handle(&mut self, msg: Inc, _ctx: Context<'_, Self, Self::Reply>) -> Self::Reply {
        self.count += msg.amount;
        self.count
    }
}

Spawning and Interacting with the Actor

// Spawn the actor and obtain its reference
let actor_ref = kameo::spawn(Counter { count: 0 });

// Send messages to the actor
let count = actor_ref.ask(Inc { amount: 42 }).await?;
assert_eq!(count, 42);

Actors and Tokio

So, what does this have to do with tokio? Actor programming is a nice paradigm but as someone who is used to working with threads, tasks end up feeling more natural. However, it turns out that these paradigms are not so different.

We need a way to send messages to other actors. Fortunately, we have an mpsc (Multi-produce; single-consumer) channel at our disposal. mpsc is perfect for our needs as we want many actors to be able to send messages, but only the one actor needs to receive them.

We need a way to spawn new actors. Fortunately, we have also a spawn method also at our disposal.

Lastly, we just need a way to define the behaviour to take when a certain message is received. For this we can use Rust enums and match to choose the behaviour.

An additional step we might want to consider is how actor replies work. Either the request includes a request ID and mailbox and the reply can be sent as a normal message, but from looking at existing frameworks, it seems more common to have a designated reply system. Thankfully tokio also offers a oneshot channel, perfect for one-off messages (such a replies).

Why you might still want actors

Actors are not a way to implement concurrent high-scale applications. They are a way to structure applications. Conveniently, this structure can scale to multi-node clusters where tokio cannot out of the box.

Ultimately with a multi-node setup you might be looking at a container orchaestrator like kubernetes to manage multiple processes on multple nodes, and then a message queue like kafka to manage sending messages to different nodes, where that node can then process the message using tokio. If you have a good actor framework, you might get this out of the box.

If you have a great actor framework, it might even be durable and allow synchronising state between nodes to allow actors to survive a node failure, although some state will inevitably be un-syncable (you cannot send a websocket connection to another node).

However, it is my opinion that the actor model is one you can still follow without needing to use an actor framework. We will take a look at this in the next chapter.