Since functions can implement traits, the solution is to define a trait that represents "can serve as callback function" and then manually implement it for every function arity up to some large number of arguments. In actix, this is done by having .to(f) take something implementing the Handler trait:
pub fn to<F, Args>(mut self, handler: F) -> Self
where
F: Handler<Args>,
Args: FromRequest + 'static,
F::Output: Responder + 'static,
Where Handler is defined by:
pub trait Handler<Args>: Clone + 'static {
type Output;
type Future: Future<Output = Self::Output>;
fn call(&self, args: Args) -> Self::Future;
}
And implementations for function arities are created as follows:
/// Generates a [`Handler`] trait impl for N-ary functions where N is specified with a sequence of
/// space separated type parameters.
///
/// # Examples
/// ```ignore
/// factory_tuple! {} // implements Handler for types: fn() -> R
/// factory_tuple! { A B C } // implements Handler for types: fn(A, B, C) -> R
/// ```
macro_rules! factory_tuple ({ $($param:ident)* } => {
impl<Func, Fut, $($param,)*> Handler<($($param,)*)> for Func
where
Func: Fn($($param),*) -> Fut + Clone + 'static,
Fut: Future,
{
type Output = Fut::Output;
type Future = Fut;
#[inline]
#[allow(non_snake_case)]
fn call(&self, ($($param,)*): ($($param,)*)) -> Self::Future {
(self)($($param,)*)
}
}
});
factory_tuple! {}
factory_tuple! { A }
factory_tuple! { A B }
factory_tuple! { A B C }
factory_tuple! { A B C D }
factory_tuple! { A B C D E }
factory_tuple! { A B C D E F }
factory_tuple! { A B C D E F G }
factory_tuple! { A B C D E F G H }
factory_tuple! { A B C D E F G H I }
factory_tuple! { A B C D E F G H I J }
factory_tuple! { A B C D E F G H I J K }
factory_tuple! { A B C D E F G H I J K L }