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:
// 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 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 props = Props::new::<Counter>();
let actor = sys.actor_of_props(props, "counter").unwrap();
actor.tell(Add, None);
actor.tell(Add, None);
actor.tell(Sub, None);
actor.tell(Print, None);
// 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. 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.