How to call a function with a dynamic number of parameters?

Hello,

I'd like to know how certain frameworks (Axum, ActixWeb, etc.) call handlers but with a different number of parameters.

Axum (example) :

use axum::{Router, routing::get, extract::State};

#[derive(Clone)]
struct AppState {}

let state = AppState {};

// create a `Router` that will be nested within another
let api = Router::new()
    .route("/posts", get(handler1)) 
    .route("/hello", get(handler2));

let app = Router::new()
    .nest("/api", api)
    .with_state(state);

async fn handler1(State(state): State<AppState>) {
    // use `state`...
}

async fn handler2() {
  // xxxx
}

how does the router know to call handler1 with the State data, and handler2 without?

is there any magic behind it (with Macros)?

looking in the ActixWeb code, I found this:

/// 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 }
factory_tuple! { A B C D E F G H I J K L M }
factory_tuple! { A B C D E F G H I J K L M N }
factory_tuple! { A B C D E F G H I J K L M N O }
factory_tuple! { A B C D E F G H I J K L M N O P }

is it with Macros that it becomes possible? or is there another trick?

1 Like

That macro you posted is defining an implementation of the Handler trait for functions taking 0 parameters, functions taking 1 parameter, all the way up to functions taking 15 parameters. It's not truly dynamic since there's a hard limit of 15, but this covers probably 99% of use cases since functions taking more than 15 parameters are rare.

Thank you for your answer,
so my suspicions are right, the only way to do it is with Macros :thinking:

You can get partway there on nightly, but I don't know if there's a way to call it (splat a tuple / unpack a Tuple).

You may find more discussion by searching for "variadic generics".

Nit: You don't need macros; you can write out all those implementations by hand...

2 Likes

thank you for this example, I see that Rust is on the right way ..

with code full of generic methods that call other generic functions made up of generic structs...
and for library use, it's not practical.

but I know it's irony, you wrote it in a very small font :laughing:

3 Likes

Bevy is another example of crates using this pattern. Here's a guide on how it implements this pattern Introductions - Dependency Injection like Bevy Engine from Scratch

3 Likes