Reputation: 17
I'm noticing more and more often that different rust crates use registration of event handlers with any set of parameters the user wants, not the library developer. I have seen this in the following crates: axum, bevy engine, teloxide.
I have a question, how exactly is this done under the hood, how does the library know what parameters the event handler needs, which can have a variable number of parameters, how are they passed to the function? I want to read something on this topic. I can't understand how the library accepts such handler methods in arguments of other methods and no macros are used.
Upvotes: 0
Views: 107
Reputation: 71430
How exactly this works depends on the specifics of the library. But the general pattern is to have a trait with a method called with some state, then implement it for every function with up to X parameters (using a macro) when parameters implement some other trait, that signifies the ability to extract the parameter from the state.
For example:
pub struct State {
// ...
}
pub trait Extractor: Sized {
fn extract(state: &mut State) -> Self;
}
pub trait Handler<Args> {
fn call(&mut self, state: &mut State);
}
// The below is usually done with a macro.
impl<F: FnMut()> Handler<()> for F {
fn call(&mut self, _state: &mut State) {
self()
}
}
impl<Arg1: Extractor, F: FnMut(Arg1)> Handler<(Arg1,)> for F {
fn call(&mut self, state: &mut State) {
self(Arg1::extract(state))
}
}
impl<Arg1: Extractor, Arg2: Extractor, F: FnMut(Arg1, Arg2)> Handler<(Arg1, Arg2)> for F {
fn call(&mut self, state: &mut State) {
self(Arg1::extract(state), Arg2::extract(state))
}
}
pub fn call_handler<Args, H: Handler<Args>>(handler: &mut H, state: &mut State) {
handler.call(state);
}
Type-erasing the handler (for example, in order to store all handlers together) can be done like the following:
pub fn handler_to_dyn<Args, H: Handler<Args> + 'static>(
mut handler: H,
) -> Box<dyn FnMut(&mut State)> {
Box::new(move |state| handler.call(state))
}
Although that is often done with an additional trait.
Upvotes: 3