Я все чаще замечаю, что в разных крейтах ржавчины используется регистрация обработчиков событий с любым набором параметров, который хочет пользователь, а не разработчик библиотеки. Я видел такое в следующих ящиках: axum, bevy engine, teloxyd.
У меня вопрос, как именно это делается под капотом, откуда библиотека знает, какие параметры нужны обработчику событий, которые могут иметь переменное количество параметров, как они передаются в функцию? Хочу почитать что-нибудь на эту тему. Я не могу понять, как библиотека принимает такие методы-обработчики в аргументах других методов и не использует макросы.
Я пробовал, но там много всего намешано. Я хотел бы увидеть где-нибудь только основной код.
Как именно это работает, зависит от специфики библиотеки. Но общий шаблон состоит в том, чтобы иметь признак с методом, вызываемым с некоторым состоянием, а затем реализовать его для каждой функции с параметрами до X (с помощью макроса), когда параметры реализуют какой-либо другой признак, что означает возможность извлечь параметр из состояние.
Например:
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);
}
Удаление типа обработчика (например, чтобы сохранить все обработчики вместе) можно выполнить следующим образом:
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))
}
Хотя это часто делается с помощью дополнительной черты.
Теперь меня смущают возвращаемые значения от обработчиков. Я добавил признак, чтобы пользователи библиотеки также могли выбирать возвращаемые значения, но я не знаю, как использовать этот новый признак в векторе обработчиков. Я добавил универсальный код R ко всему коду: rust pub trait Handler<Args, R> { fn call(&self, state: &mut State) -> R; }
@JohnDarkman Возвращаемое значение также должно обрабатываться единообразно. Обычно он реализует некоторый признак (например, IntoResponse
), который позволяет превратить его в некоторый общий тип (Response
). В impl Handler for Fn
мы преобразуем возвращаемый тип в Response
, и call()
возвращает Response
.
Все понял, добавил еще одну структуру для хранения результата и черту, которая преобразуется в эту структуру, вроде работает. Спасибо.
Как черта, которая реализуется для X параметров. Почему бы вам не взглянуть на их реализацию?