7

Some libraries such as Bevy or Actix Web have functions which accept user defined functions with any amount of parameters.

Actix Web:

async fn fn1(path: web::Path<String>) -> impl Responder {
    // not important 
}

async fn fn2(_req: HttpRequest) -> impl Responder {
    // not important
}

let app = App::new()
    .route("/", web::get().to(fn2))
    .route("/{name}", web::get().to(fn1));

Bevy:

fn fn1(mut commands: Commands) {}
fn fn2(mut commands: Commands, time: Res<Time>) {}

App::new().add_system(fn1).add_system(fn2);

As you can see in both cases the functions web::get().to(), add_system() accept functions with dynamic number and types of parameters as their parameter. They're not macros. How can I achieve this? Is there a name for this? Thanks

2
  • 3
    It's not pretty... Bevy docs/src Commented Feb 11, 2022 at 16:20
  • promethia-27 wrote an online book on this topic. Commented Jun 29, 2023 at 9:23

1 Answer 1

8

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 }
Sign up to request clarification or add additional context in comments.

1 Comment

promethia-27 wrote an online book on this topic.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.