Restioson / xtra

🎭 A tiny actor framework
Mozilla Public License 2.0
321 stars 36 forks source link

trait for actors handling multiple message types #179

Closed bobdebuildr closed 2 years ago

bobdebuildr commented 2 years ago

Hello I have been trying out the master branch of xtra for a small project, and I have a question regarding the Handler implementations. Is it somehow possible to define a trait that requires a certain set of messages, e.g. A and B to be handled, by requiring implementations of Handler<A>, Handler<B> to exist? Ideally I would be able to write something like this:

// the trait
pub trait Animal: Handler<Eat> + Handler<Sleep> {}
// default impl for convenience
impl<T> Animal for T where T: Handler<Eat> + Handler<Sleep> {}

// not sure about this...
let animals: Vec<Address<Box<dyn Animal>>> = ...;
// this is what I want to get at
for a in animals {
    a.send(Eat{}).await;
    a.send(Sleep{}).await;
}

I guess this is similar to a MessageChannel, but for multiple handlers. Does this even make sense or is there some other, better way to do this?

thomaseizinger commented 2 years ago

I can think of multiple ways to solve this:

  1. Make an enum with all message types and implement Handler for that.
  2. Have seperate list of MessageChannels, one per message. MessageChannels are cheap to clone.

Do these Addresses point the same actor type or different ones?

bobdebuildr commented 2 years ago

Thanks for your comments, that makes sense. I would have different actor types, in the example above e.g. DogActor and CatActor, both implementing the two Handlers. In my case, the types to be handled are already enums with lots of variants, so I think I would prefer option 2.

thomaseizinger commented 2 years ago

I have personally used (2) before and it worked well. You can also do something like this which cuts some of the boilerplate:

struct Channels {
    ch1: MessageChannel<Foo>,
    ch2: MessageChannel<Bar>
}

impl Channels {
    fn new<A: Handler<Foo> + Handler<Bar>>(addr: Address<A>) -> Self {
        Self {
            ch1: MessageChannel::new(addr.clone()),
            ch2: MessageChannel::new(addr)
        }
    }
}

That would avoid needing to pass the address to the same actor multiple times but allow for different actors serving as the backing one for the channels.

Hope this helps!

Restioson commented 2 years ago

Overall, I'm not 100% sure if it's possible for xtra to provide some kind of combination message channel like this, unfortunately (how would this be expressible through the rust typesystem?). I think that the solution to the actual problems themselves have been discussed here though.