Multi-type Messaging
In real world applications, actors will typically receive different message types and execute different behaviors based on the type received.
So far you've seen a simple example where an actor's message type is defined in the Actor::Msg
associated type.
More specifically, this defines an actor's mailbox type. To allow an actor to receive multiple message types,
Riker provides a Receive<T>
trait and the #[actor]
attribute.
Let's see how these are used:
use actors_rs::*;
use std::time::Duration;
// Define the messages we'll use
#[derive(Clone, Debug)]
pub struct Add;
#[derive(Clone, Debug)]
pub struct Sub;
#[derive(Clone, Debug)]
pub struct Print;
// Define the Actor and use the 'actor' attribute
// to specify which messages it will receive
#[actor(Add, Sub, Print)]
struct Counter {
count: u32,
}
impl ActorFactoryArgs<u32> for Counter {
fn create_args(count: u32) -> Self {
Self { count }
}
}
impl Actor for Counter {
// we used the #[actor] attribute so CounterMsg is the Msg type
type Msg = CounterMsg;
fn recv(&mut self, ctx: &Context<Self::Msg>, msg: Self::Msg, sender: Sender) {
// Use the respective Receive<T> implementation
self.receive(ctx, msg, sender);
}
}
impl Receive<Add> for Counter {
type Msg = CounterMsg;
fn receive(&mut self, _ctx: &Context<Self::Msg>, _msg: Add, _sender: Sender) {
self.count += 1;
}
}
impl Receive<Sub> for Counter {
type Msg = CounterMsg;
fn receive(&mut self, _ctx: &Context<Self::Msg>, _msg: Sub, _sender: Sender) {
self.count -= 1;
}
}
impl Receive<Print> for Counter {
type Msg = CounterMsg;
fn receive(&mut self, _ctx: &Context<Self::Msg>, _msg: Print, _sender: Sender) {
println!("Total counter value: {}", self.count);
}
}
fn main() {
let sys = ActorSystem::new().unwrap();
let actor = sys.actor_of_args::<Counter, _>("counter", 0).unwrap();
actor.tell(Add, None);
actor.tell(Add, None);
actor.tell(Sub, None);
actor.tell(Print, None);
sys.print_tree();
// force main to wait before exiting program
std::thread::sleep(Duration::from_millis(500));
}
In this example, we've used #actor[Add, Sub, Print]
to set up the actor to receive Add
, Sub
and Print
types. For each of these, the Receive<T>
trait is implemented on the actor, defining how each message should be handled.
Note
When using the #[actor()]
attribute, the actor's Msg
associated type should be set to '[DataType]Msg'. E.g. if an actor is a struct named MyActor
, then the Actor::Msg
associated type will be MyActorMsg
.
By utilizing Receive<T>
and #[actor]
, complex message handling can be defined clearly and concisely.
TODO: For more advanced messaging examples see Advanced Messaging.
In the next section, we'll explore the relationship between actors and how actors form a hierarchy.