Many questions/problems regarding generics and type parameters

Hi,

I have the following code:

struct EventA {
    foo: String,
}

struct EventB {
    bar: String,
}

enum EventHandler<FNW, ARGW>{
    EventA(FNW<Box<dyn Fn(ARGW<EventA>) + Send + Sync + 'static>>),
    EventB(FNW<Box<dyn Fn(ARGW<EventB>) + Send + Sync + 'static>>),
}

(There are around 150 event types that I generate automatically from a JSON file.)

(Here is a Playground with the code from this post)

But the compiler rejects this with "type argument not allowed":

error[E0109]: type arguments are not allowed for this type
  --> src/bin/main.rs:15:16
   |
15 |     EventA(FNW<Box<dyn Fn(ARGW<EventA>) + Send + Sync + 'static>>),
   |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type argument not allowed

Is there any way I can do this?

Here is what I try to archive with the code above:

EvenHandler is a wrapper of event callbacks (so a user of my crate can store callbacks for different events in a collection).

The first type parameter (FNW) is a wrapper for the boxed closure, so a user can select a collection type (this is useful if there are several callbacks for the same event):

    // tupple
    let _ = EventHandler::EventA((
        |_: Rc<EventA>| (),
        |_: Rc<EventA>| (),
    ));

    // array
    let _ = EventHandler::EventA([
        |_: Rc<EventA>| (),
        |_: Rc<EventA>| (),
    ]);

    // custom struct
    let _ = EventHandler::EventA(MyEventHandles {
        internal: |_: Rc<EventA>| (),
        external: |_: Rc<EventA>| (),
    });

Here my first problem arises:

I'd also like to support the usage of a HashMap (and similar types), but HashMaps have two type arguments (or even 3, I'm not 100% sure...).

I tried the following, but that results in a syntax error:

    let event_a_handlers = HashMap::new();
    event_a_handlers.insert("foo", |_: Rc<EventA>| ());
    event_a_handlers.insert("bar", |_: Rc<EventA>| ());
    let _ = EventHandler::EventA::<HashMap<String, _>(event_a_handlers);

(I was hoping, that I could pass a type parameter that has itself one type parameter missing that the compiler will insert later.)

Is it somehow possible to archive this without an additional KeyValueEventHandler type that has two type parameters?

My second problem is regarding the second type parameter ARGW:

EventA(FNW<Box<dyn Fn(ARGW<EventA>) + Send + Sync + 'static>>)

As mentioned, the compiler rejects this type parameter with "type argument not allowed" – how can I archive this?

I need this type parameter so users can wrap the argument of the callback if needed. For example, I need (in another crate that uses this crate) to wrap the parameter in a Rc, but others might need to wrap it in an Arc or something completely different:

    // `Rc`
    let _ = EventHandler::EventA((
        |_: Rc<EventA>| (),
        |_: Rc<EventA>| (),
    ));

    // `Arc`
    let _ = EventHandler::EventA((
        |_: Arc<EventA>| (),
        |_: Arc<EventA>| (),
    ));

I also was hoping, that I somehow can support that event callbacks can accept / request references to the events (instead of owned values).

It seems AsRef could be used for this:

    let _ = EventHandler::EventA((
        |_: dyn AsRef<EventA>| (),
        |_: dyn AsRef<EventA>| (),
    ))

But I don't like this option very much because now the user has to call as_ref() to get the actual reference.

And last:

Many users of my crate (if there will be any :laughing:...) probably don't need to wrap the boxed closure and/or the argument of the closure in another type.

Because of that, I'd like to offer this option as well.

At the moment I think my only option is to create additional types that don't have these type parameters, which would mean 3 additional types.

Therefore I wonder if there is any way the following would be possible with EventHandler from above:

let _ = EventHandler::EventA(|_: EventA| ());

(To me it seems this is not possible, but maybe someone has an idea how this would be possible).

I feel, if I want to offer everything that a user could need, I need to generate 20 different types, which would IMO result in a shitty API.

I'd be thankful for every tip or hint, even for completely different approaches! :slight_smile:

Here is again the link to the Playground, if someone wants to give it a try.

Hi there, let's first look into this part of your code:

The enum you are defining contains two generic type parameters FNW and ARGW. Generic type parameters are kind of place holders for actual types. So they cannot be generic on it's own.
So FNW<some type> is just invalid syntax here.

From what I guess is, that you'd like to define a generic type representing a closure / function pointer. So Box<dyn Fn(Type)> defines already a trait object that is kind of generic as it can hold any function pointer that adheres to the function signature given.

The same applies to ARGW. As it is just a generic type parameter.

So as in your enum the variants do already specify the actual type to be used in the function parameter definition you could simply do:

enum EventHandler {
    EventA(Box<dyn Fn(EventA) + Send + Sync + 'static>),
    EventB(Box<dyn Fn(EventB) + Send + Sync + 'static>),
}

However, as the trait object definition is used quite similar in the cases you might want to define a type alias for it, like so:

type EventFunction<T> = Box<dyn Fn(T) + Send + Sync + 'static>;

enum EventHandler {
    EventA(EventFunction<EventA>),
    EventB(EventFunction<EventB>),
}

Let's tackle the other issues you have:

The thing you try to achieve with storing different "kinds" of event handlers in the enum variants, either as function, as tuple, as array or custom struct is - from what I know - not possible. The reason for this is that you cannot restrict a generic type parameter to actual types, but only with trait bounds.

So this would lead to something like

type EventFunction<T> = Box<dyn Fn(T) + Send + Sync + 'static>;

trait MyHandlerType {}

impl<T> MyHandlerType for (EventFunction<T>, EventFunction<T>) {}
// you need to list each arry size you would like to support
impl<T> MyHandlerType for [EventFunction<T>; 1] {}
impl<T> MyHandlerType for [EventFunction<T>; 2] {}
impl<T> MyHandlerType for [EventFunction<T>; 3] {}

struct CustomType<T> {
    internal: EventFunction<T>,
    external: EventFunction<T>,
}

impl<T> MyHandlerType for CustomType<T> {}

enum EventHandler<T> 
    where T:  MyHandlerType
{
     EventA(T),
    EventB(T),
}

In this szenario every the trait MyHandlerType would need to provide a method to call the actual handlers. And this function would need to be implemented on each different type. I'm not sure whether this is really what you wanted to achieve ?

Your attempt to use a HashMap here is also assuming that you could store arbitrary generic types in your enum I guess. However, you could generelaze to store only HashMap handlers as follows:

use std::collections::HashMap;
use std::rc::Rc;

struct EventA {
    foo: String,
}

struct EventB {
    bar: String,
}

type EventFunction<T> = Box<dyn Fn(Rc<T>) + Send + Sync + 'static>;

enum EventHandler {
    EventA(HashMap<String, EventFunction<EventA>>),
    EventB(HashMap<String, EventFunction<EventB>>),
}

fn main() {
    let mut event_a_handlers = HashMap::<String, EventFunction<_>>::new();
    event_a_handlers.insert(
        "foo".to_string(),
        Box::new( |_: Rc<EventA>| {} )
    );
    event_a_handlers.insert( 
        "bar".to_string(),
        Box::new(|_: Rc<EventA>| {} )
    );
    let _ = EventHandler::EventA(event_a_handlers);
}

Hope this helps a bit....

1 Like

To add to the above answer, the feature you need is the ability to be polymorphic over higher kinded types. Rust might eventually have this feature, but for now you will have to work around it.

My advice is to make a Handler trait:

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

And then pass around these if needed, but I don't know enough about the rest of your code to say.

1 Like

Thank you for your help, @2ndTaleStudio, and @Kestrer! :slight_smile:

Damn, I was sure this would solve my problem in a simple way... :laughing:

To show the bigger picture of what I try to archive – and in case I do something stupid – I have the following Playground that shows a model of my current event system:

Playground

This code works perfectly fine with a single callback per event.

I'm now trying to make it possible to install two callbacks per event, without duplicating the work of my EventHandler::handle method (callbacks also have to be removable and replaceable, which makes this problem difficult).

It also would be very useful, if a user could define the callback argument type separately (for example (EventFunction<&EventA>, EventFunction<EventA>)).

It seems I now need to introduce a trait like the above EventHandlerType which will be implemented for all inner collection types that EventHandler should support (which will have the handle method that my EventHandler type has at the moment).

This solution would be perfect if a user of the crate could implement it. The problem is, that only my code generator can implement the handle method (because there are more than 150 events).

And this would prevent the user to be able to request specific types for the callback arguments (I think).

So I'd have to provide these EventHandlerType implementations myself (which could easily result in 20 types if I combine them with wrappers around the events that are passed to the callbacks (Rc, Arc, etc.).

Anyway, tomorrow I will try to implement the EventHandlerType approach from above, but (hopefully) someone will tell me before, that I do something unnecessary, and that there is an easier way... :slight_smile:

Well I guess this is the thing I would kind of question. Why is it required to give the user this much freedom to register their event handler ? Could it not be sufficient to define a general signature for the closures passed as event handlers? If you require to register and unregister eventhandlers you should provide a register function that stores the handler itself in a HashMap and hands out an identifier/key into it - could be a generated number for example. This identifier could than be used to unregister the event handler at a later time.

I'm also not sure why for example the user of your API could provide a consuming and a non-consuming function of the handler. (EventFunction<&EventA>, EventFunction<EventA>) How would your internal function to call the handler decide which version to call ?

1 Like

I'm also not sure why for example the user of your API could provide a consuming and a non-consuming function of the handler. (EventFunction<&EventA>, EventFunction)

I will be a user of the crate myself, and (EventFunction<&EventA>, EventFunction<EventA>) could make sense for me there.

Basically, the first callback is an internal callback that doesn't need an owned value, and the second callback is a user-defined callback (a user of my second crate).

Of course, I could also pass a reference to the user callback. But I don't need the event anymore, so I thought a callback API that provides owned values to end-users makes the most sense.

How would your internal function to call the handler decide which version to call ?

This part is implemented by the user of crate 1. Crate 1 itself only implements protocol types (for example events) and helpers that can't be implemented by users of crate 1 (for example EventHandler::handle).

Why is it required to give the user this much freedom to register their event handler ? Could it not be sufficient to define a general signature for the closures passed as event handlers?

I don't want to tie crate 1 to close to crate 2, and I'd like to make crate 1 flexible if possible.

For some use cases, it would make sense to not wrap the callback arguments in a Rc or Arc (for example, if only one callback per event is allowed).

If several callbacks per event are allowed, Rc or Arc (or cloning) would be required if owned event data should be passed to callbacks.

I also think it's good for learning Rust if I don't always go the easiest route and try to implement things as well as I can.

If you require to register and unregister eventhandlers you should provide a register function that stores the handler itself in a HashMap and hands out an identifier/key into it - could be a generated number for example. This identifier could than be used to unregister the event handler at a later time.

That is not the problem (in the playground above I use the TypeId of the event type to store event handlers, which works great).

What I meant is, that without the requirement to delete and replace an event handler (either internal or external, as mentioned above) I just could wrap the internal and/or external event handler in a closure and install that closure as event handler (I hope it is clear what I mean).

The problem is, if I want to remove or replace an internal or external event handler. If both event handlers are installed and I want to remove one (i.e. I delete my wrapper closure), I don't have access to the other event handler to recreate my wrapper closure.

Well to achieve this you could internally wrap the internal and external handlers in an Option. Removing one of them would leave the corresponding Option beeing None. To restore the value of one of the options yu would not need both closures to be known you can mutate the structure / tuple and store the new closure in the Option - Just a suggestion asI might still not get the full picture of your idea - excuse my ignorence here please :wink:

I thought about something similar, but for some reason, it wasn't possible. But, if I remember correctly, I didn't think about Option::take... And I also learned a few new things, that could have changed the situation.

Tomorrow I will try that first, thanks! :slight_smile:

Ah, now I remember what the problem was.

Basically, my approach was to create a struct that held the callbacks:

struct EventHandlerCallbacks<E> where E: EventTrait {
    internal: Option<Box<dyn Fn(&E) + Send + Sync + 'static>>,
    external: Option<Box<dyn Fn(E) + Send + Sync + 'static>>,
}

Then I created a seconds DashMap (a concurrent HashMap) for this new struct:

struct Browser {
    event_handlers: DashMap<TypeId, EventHandler>,
    event_handler_callbacks: DashMap<TypeId, Arc<EventHandlerCallbacks>>,
}

In my add_event_handler method, I then could match EventHandlerCallbacks and create an event handler that called the internal and/or external event handlers.

The problem is that EventHandlerCallbacks needs the E: EventTrait type parameter, so event_handler_callbacks could only store a single type of event.

The only reason for EventHandler is to remove this requirement.

Your suggestion would hit the same limitation, right? Because I have to store the Option somewhere, which would result in the problem above (I have to specify the type of the events).

As far as I know, I can't use trait objects for the events, because with trait objects event handlers lose access to the actual event data.

Here is a playground with my full code, if someone is interested to have a look.

Damn, this solution would be perfect... :laughing:

Hey,
well if there is the need to use this struct for different kind's of E to store them in a HashMap or similar you could make the parameter a trait object and use downcast on it to cast it to the actual type.

I've prepared a lightweight example in this Playground

It does not contain your EventHandler enum and some of the other complex stuff I'm not fully grasping. But with the code given you could register a handler for any event hat implements a specific trait. The event handler for a specific event does the downcast into the proper event to get access to it's specifc contents.

Hope this helps a bit :wink:

Thanks for your help, @2ndTaleStudio!

An earlier version of the code used Any in a similar way, but I thought it should be possible to archive my goal without Any (because I have complete control over the code, and even use a code generator).

I now probably spend at least 7 full days trying to create an event system that's as I'd like it to be, but I think I will give up now :laughing:

(Maybe in the future I will come back and try again).

The code of crate 1 and crate 2 both would be simpler with Any, so that's a benefit (even if performance decrease a bit).

(I think I will start a new thread soon in which I ask if Any is as bad as I thought. From my research yesterday, it doesn't seem so bad in my case).

Anyway, without your reminder of Any I probably would waste even more time, so thanks again! :slight_smile:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.