Take a List of things and call only specific things in the List based on Data

Here's the basic goal:

We have a set of Events. We have a set of EventHandlers that use specific subsets of the Events. Some handlers handle one of the events or a few specific ones or all. By "handle", I mean the handler will return a Command in response to the event. It will always return a Command. Seemed natural to write the Events as an enum

enum Event {
   Event1,
   Event2,
   Event3,
}

We need to collect all the arbitrary number of event handlers into a list. The number and type of event handlers varies on each list and who's using that list. So not every list is the same.

Now here's the problem. When we get an event, we need to iterate over our list of event handlers and pass them the event. Not all event handlers can handle each and every single type of event, so it follows we must return from the event handlers a Option in case they are passed an event they don't handle.

The alternative is to expression match on events before passing them to handlers and only pass the event to a handler it can handle. I don't like this way. But I kinda don't like the Option way either. Seems wrong to pass an event to a handler that doesn't care about it. Yet, we have a list of arbitrary handlers. Is there some way to give to a set of handlers an event but only the handlers that care about that event, maybe through some combination of conditional enum variants/generics/const generics/patterns? I almost believe the advanced type system can help me here.

I was thinking one way I could do this in a fictional type system would be to

enum Event {
    EventA,
    EventB,
}

struct EventHandlerLists {
    ehlA: Vec<Handler<Event::EventA>>, // These aren't really that important. I could very well settle for a EventAHandler concrete type. The function overloading below is what's important.
    ehlB: Vec<Handler<Event::EventB>>,
}

impl EventHandlerLists {
    fn handle(event: Event::EventA) {
        for handler in self.ehlA {
            handler.handle(event);
        }
    }

    fn handle(event: Event::EventB) {
        for handler in self.ehlB {
            handler.handle(event);
        }
    }
}

event_handler_lists.handle(eventA);
event_handler_lists.handle(eventB);

I'm pretty sure I butchered that somehow, but that's the general idea. Some kind of function overloading based on enum variant parameter type.

1 Like

Why not use traits?

trait Event {}

struct EventA;
struct EventB;

impl Event for EventA {}
impl Event for EventB {}

trait Handler<E: Event> {
    fn handle(&mut self, event: E);
}

Any handler that implements the Handler trait can take any Event. Which is the behavior I want to be restricted. In fact, I already do this, because it has to be done anyway as a first step, but it's a point that's aside the main issue. The issue is, I want specific Handlers to only handle specific Events, no more, no less. But also, the place that hands the events to the handlers must see all events as the same. This is kind of like refinement types on top of function overloading, if that makes sense.

Well, you could just implement the trait for specific Events

struct EventHandlerLists {
    ehlA: Vec<Box<dyn Handler<EventA>>>,
    ehlB: Vec<Box<dyn Handler<EventB>>>,
}

impl Handler<EventA> for EventHandlerLists {
    fn handle(&mut self, event: EventA) {
        for eh in self.ehlA.iter_mut() {
            eh.handle(event.clone())
        }
    }
}

impl Handler<EventB> for EventHandlerLists {
    fn handle(&mut self, event: EventB) {
        for eh in self.ehlB.iter_mut() {
            eh.handle(event.clone())
        }
    }
}

Inner structs can kind of help:

enum Events {
    Event1(Ev1)
    Event2(Ev2)
    Event3(Ev3)
}

struct Ev1;
struct Ev2;
struct Ev3;

fn handle_for_receiver_a(ev: Event) {
    match ev {
        Event::Event1(ev1) => handle_ev1(ev1),
        Event::Event2(ev2) => handle_ev2(ev2),
        _ => {},
    }
}

fn handle_for _receiver_b(ev: Event) {
    match ev {
        Event::Event1(ev1) => handle_ev1(ev1),
        Event::Event3(ev3) => handle_ev3(ev3),
        _ => {},
    }
}

fn handle_ev1(ev1: Ev1) {};
fn handle_ev2(ev2: Ev2) {};
fn handle_ev3(ev3: Ev3) {};

A good std example of this pattern is IpAddr, which is divided into Ipv4Addr and Ipv6Addr. Some methods are implemented on just one or the other type. Others are implemented on the enum, and often delegate down to alternative implementations on the inner structs.

EventA and EventB are just structs though, and that's the cruz of the issue. I need them to be all the same type view to the thing calling EventHandlerLists. They would need to be an enum. For example,

impl SomeOtherTypeBeforeTheEventHandlerLists
fn tell_them_to_handle(&self, event: Event) {
    self.eventHandlerLists.handle(event)
}

Notice how that method views Event as a single type? Now back to the EventHandlerLists, it understands there is variance on that type Event::A, Event::B, Event::SomethingElse, etc. It holds lists of handlers. It needs not mess with pattern matching Event types to know which handler to handle which event. Because, remember, each handler can only handle specific types of events (Event::A or a Event::b), not all of the variants. Furthermore, the EventHandlerLists must also not mess with bothering to know if it HAS a handler that can handle the specific event, hence why I place handlers into Vec and iterate over it. If its empty. No harm.

Per code in my previous example.

for handler in self.ehlA {
            handler.handle(event);
        }

How is this different from my example?

You can use my example like so

use Handler;

let handler_list : EventHandlerLists;

// init handler_list

handler_list.handle(EventA);
handler_list.handle(EventB);

And to the user it is just like overloading in other languages. (This is what you said was the important bit)

If you need to generically use Handler, you can do

fn use_handler<E: Event>(&mut self, e: E) where Self: Handler<E> {
    if self.some_condition() {
        self.handle(e)
    }
}

This is the same as what I put in the impls of Handler<...> for EventHandlerLists

I have one list of Handlers. I have one list of Events. With your trait example, I would need a separate implemented list of Handlers for EventA and separate implemented list of Handlers for EvenB. Worse, that type information needs to be resolved before making the call from one place.

And I would need to know which one to invoke for which specific Event. I'm saying I don't want to evaluate which type of Event. Lets rewind so its easier to understand the goal here. Forget Handlers and Events for a sec. We have main

impl Handler<EventA> for EventHandlerLists
impl Handler<EventB> for EventHandlerLists
fn main() {
    let event_a = // fictitious code here
    let event_b = // fictitious code here
    let event_c = // fictitious code here

    let handler_for_a_and_b = // fictitious code here
    let handler_for_just_c = // fictitious code here
    
    // On this line how do you propose we take a random event, either a or b or c, and have it handled?

I thought about using that pattern; I've used it before, but for a different purpose. In a SIP crate I'm making, I use it as a intermediary storage of data for the builder pattern.

enum RequestData {
    Uri(Uri),
    Method(Method),
    Headers(Vec<Header>)
}

struct Builder {
    data: Vec<RequestData>
}

impl MessageBuilder {
       fn add_method(&mut self, method: Method) -> &mut Self {
        self.data.push(RequestData::Method(method));

        self
    }
    ...
}

Then when it comes time to call build() on the builder, it collects all of the data in its field and constructs sip messages. I have to do this until until anonymous variant types RFC for anonymous variant types, a minimal ad-hoc sum type by eaglgenes101 · Pull Request #2587 · rust-lang/rfcs · GitHub. But it won't help me here, even to sort of kinda subtype enums. For one, each enum variant can only meaningfully hold one struct concrete type. You could put more but the algebra gets messy

enum EnumLimiter {
    ForHandlersHandlingEventA(EventA)
    ForHandlersHandlingEventAAndEventB(EventA, EventB) // ? or i think i would need { } fields anyway

    etc ect
}

I think you see what I mean. Either way, the pattern matching in your example is what I'm trying to avoid also.

Ok, in your original reply you showed two lists of handlers, and that threw me off.

In that case you can use an enum and match on it or use trait objects, these are the only ways to dynamically pick between different types. (Different events must be different types or at least different variants of an enum).

enum PickEvent {
    EventA(EventA),
    EventB(EventB),
    EventC(EventC),
}

fn main() {
let event_a = // fictitious code here
    let event_b = // fictitious code here
    let event_c = // fictitious code here

    let handler_for_a_and_b = // fictitious code here
    let handler_for_just_c = // fictitious code here

    let event : PickEvent = // (one of event_a, event_b, event_c)

     match event {
        PickEvent::EventA(a) => handler_for_a_and_b.handle(a),
        PickEvent::EventB(b) => handler_for_a_and_b.handle(b),
        PickEvent::EventC(c) => handler_for_just_c.handle(c),
    }
}

Somewhere, your handlers are going to have to express which event types they can handle. I'm not sure why a match is a bad place to do that, but here are some alternatives:

The handler filters the list directly before passing the enum to its handle function:

impl Handler for HandlerA {
    fn handle_list(&self, list: EventList) -> Vec<HandleResult> {  
        list.iter()
            .filter(|ev| ev.is_ev1() or ev.is_ev2())
            .map(self.handle_ev1_or_ev2)
            .collect()
    }
    fn handle_ev1_or_ev2(ev: Event) -> HandleResult {}
}

If you don't like that, you could have your EventList act more like a pub-sub broker (call it EventBroker), and dispatch events of the type the handlers want. In that case, the handlers would have to register with the EventBroker)

But there's no way to make the type system remove elements from a vec for you without some code somewhere.

1 Like

You mean this?

event_handler_lists.handle(eventA);
event_handler_lists.handle(eventB);

event_handler_lists is one variable? One list? Or do you mean the fields inside EventHandlerLists?

ehlA: Vec<Handler<Event::EventA>>
ehlB: Vec<Handler<Event::EventB>>

That's just the trick the EventHandlerLists uses. From everyone using EventHandlerLists, it's one thing. They don't know about its fields or how its doing its inner stuff.

match event {
        PickEvent::EventA(a) => handler_for_a_and_b.handle(a),
        PickEvent::EventB(b) => handler_for_a_and_b.handle(b),
        PickEvent::EventC(c) => handler_for_just_c.handle(c),
    }

So this code is what I'm trying to avoid. So one trick I've come up with as a possible solution, is to hide both Event and Handler into something. Like a struct acting as a list of them. So now that they are hidden inside something else I can act on them as a single thing

let handler_for_a_and_b
let handler_for_just_c

let handler_hider.push(handler_for_a_and_b)
let handler_hider.push(handler_for_just_c)

Why would I want to do this? Because now

fn do_something_with_this(what_is_this_crazy_event_idk: Event) {
    self.handler_hider.handle(what_is_this_crazy_event_idk);
}

See that? One handler_hider. That code there is the important part. We don't want to have a hundred different handler_hiders of different trait implementations AND then have to do some kind of variance pattern matching on the what_is_this_crazy_event_idk to pass it the one handler_hider implementation that takes it. Not because writing a hundred impls is exhausting. But because writing the pattern matching or logic to align what event goes into what handler_hider implementation is exhausting and brittle. Function overloading to the rescue. One struct. One handler_hider. Many functions called handle() taking different types of what_is_this_crazy_event_idk but all those functions are named the same. And you know what we can do to functions that are named the same but take different typed arguments? Not have worry about logic of which function to call. We call the one true name and its resolved at compile time. Static checking on top of it all and its nicely monomorphized. No dynamic behavior at runtime except the Vec and the other dynamic stuff that maybe gets used in this pattern, but that's not the point. I get why Rust doesn't support function overloading, and I think its a good thing it doesn't. I'm trying to come up with a hack here that gets me close for my specific case.

If you are fine with having multiple event handler lists, you can do

struct EventHandler {
    event_a: Vec<Box<dyn Handler<EventA>>>,
    event_b: Vec<Box<dyn Handler<EventB>>>,
    event_c: Vec<Box<dyn Handler<EventC>>>,
    // ... more handlers ...
}

impl Handler<EventA> for EventHandler {
    fn handle(&mut self, event: EventA) {
        for handler in self.event_a.iter_mut() {
            handler.handle(event);
        }
    }
}

// same for other events,

Then you can do, this without any issues

impl EventHandler {
    fn do_something_with_this<E>(&mut self, what_is_this_crazy_event_idk: E) where Self: Handler<E> {
        self.handler_hider.handle(what_is_this_crazy_event_idk);
    }
}

and you can create an enum if you want to dynamically pick which type of event you want to handle and impl Hander<Enum> for EventHandler

impl Handler<Enum> for EventHandler {
    fn handle(&mut self, event: Enum) {
        match event {
            Enum::A(event_a) => self.handle(event_a),
            Enum::B(event_b) => self.handle(event_b),
            Enum::C(event_c) => self.handle(event_c),
        }
    }
}

Now the match is isolated to a single function and you don't need to think about it. Even better, adding this last impl enables do_something_with_this to work with the Enum automatically. You only need to make this enum once and implement the handler once.

Finally, if you think this is a lot of boiler plate, then you can use a macro (macros are made to get rid of boilerplate), now it's not brittle (you only need to change the macro call, and everything changes).

edit: adding a register_handler function to playground example

1 Like

First of all, wow! I love that you were thoughtful enough to wrap these in a macro to reduce boilerplate. I'm still learning Rust macros, so this is somewhat beyond me but I'm willing to dig into the Rust macro book tonight to reciprocate and I do have some boilerplate in other patterns I use that could probably use some macro hiding.

But I don't know how this part of your code would work

impl EventHandler {
    fn do_something_with_this<E>(&mut self, what_is_this_crazy_event_idk: E) where Self: Handler<E> {
        self.handler_hider.handle(what_is_this_crazy_event_idk);
    }
}

Which is of course the most important piece of all this. Everything else is in service of this one goal. Could you be explicit on how EventHandler defines handler_hider in its field to call it like this self.handler_hider.handle(what_is_this_crazy_event_idk);?

With the macro that I showed, do_something_with_this is just handle. If you need to, you can also wrap E in DynEvent before handing it off to handle, but that's about it.

Note: I used &mut E for Handler::handle to allow it to be used multiple times (when iterating), but you could change that to &E or Clone::clone(&event) (note not event.clone() because that isn't guaranteed to call the Clone trait's clone) if E: Clone or just copy it if E: Copy.

Feel free to take the macro and the trait and change it to however you want.

impl EventHandler {
    fn do_something_with_this<E: Event>(&mut self, what_is_this_crazy_event_idk: E) where Self: Handler<E> {
        self.handle(what_is_this_crazy_event_idk);
    }
}

My macro knowledge is really bad right now. Can you give me an example of how I would use the code? Something like a main() example.

For now here's what I what I came up with, to sorta simulate function overloading. If we could specify Enum Variants as types in Rust, then that would get rid of the match in handle() and would remove the structs in enums pattern. https://github.com/rust-lang/rfcs/pull/1450 would get rid most of the boilerplate here.

Any new Receptor or Event just need the match expression, impl OverloadedReceptor and Receptor field updated. If you don't update them, then no harm, if you try to use an event not handled then it's a compile error. The main thing is, the code doesn't need any test cases. It's guaranteed to be safe, because while the Receptors hider deals with a variance of Events, each individual Receptor handles one and only one Event.

Whereas before I did this, I had something like this

impl .... Prompter {
    fn handle(event: Event) -> Option<Command> {
        match event {
            Event::EventA => ...
            Event::EventB => ...
            Event::EventC => ...
            Event::EventD => ...
            Event::EventE => ...
            Event::EventF => ...
            Event::EventG => ...
            Event::EventH => ...
}

It should handle the EventA case but if with dozens of cases I make a mistake when implementing this for the first time or during a refactor and I have it handle EventB or EventC or etc then its a bug the type system doesn't catch. Which means I would need a unit test for this. Plus I would need to wrap -> Command in an Option, incase it doesn't handle the event. Which means another match expression for whoever gets the Command. Don't have to worry about that anymore.

The current enum variants as types RFC to follow is this one

https://github.com/rust-lang/rfcs/pull/2593

Here is a playground example on how to use the macro

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.