Tomáš Gavenčiak
Tomáš Gavenčiak

Reputation: 490

Calling functions with different numbers of arguments in Rust macros

I need a macro that will call functions with different numbers of arguments or a macro that will generate a valid argument list from its (repeating) parameters.

I am fine with explicitly giving the information about the number of arguments to the macro, but I can't figure out how to generate the argument list for the function - I always stumble on the macros returning expressions rather than token tree.

I made the following playground example:

macro_rules! call (
    ($f: expr, $($params:tt)*) => {
        $f(make_params!($($params:tt)*))
    };
);

macro_rules! make_params {
    () => {};
    (I $($params: tt)*) => {
        1, make_params!($($params:tt)*)
    };
}


fn foo(a: i32, b: i32, c: i32) {
    println!("foo: {} {} {}", a, b, c);
}

fn bar(a: i32, b: i32) {
    println!("bar: {} {}", a, b);
}

fn main() {
    call!(foo, I I I);
    call!(bar, I I);
}

The compiler complains with the following:

error: macro expansion ignores token `,` and any following
  --> src/main.rs:10:10
   |
10 |         1, make_params!($($params:tt)*)
   |          ^
   |
note: caused by the macro expansion here; the usage of `make_params!` is likely invalid in expression context
  --> src/main.rs:3:12
   |
3  |         $f(make_params!($($params:tt)*))
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...

How can I treat the return of make_params! as a token stream (or such) rather than expression?

My real use case is a bit more involved than this toy example. My functions have multiple parameter types which are constructed in different ways. In my case, just making macros call1, call2!, ... does not seem like a good solution, as I would need the likes of call_IIOOI, call_IIIO, etc.

Upvotes: 4

Views: 4729

Answers (1)

Jmb
Jmb

Reputation: 23463

You need to build the function call progressively as you go and only emit it at once in the end:

macro_rules! call (
    ($f: expr, $($params:tt)*) => {
        make_call!($f, () $($params)*)
    };
);

macro_rules! make_call {
    ($f: expr, ($($args:tt)*)) => { $f($($args)*) };
    ($f: expr, () I $($params:tt)*) => {
        make_call!($f, (1) $($params)*)
    };
    ($f: expr, ($($args:tt)*) I $($params:tt)*) => {
        make_call!($f, ($($args)*, 1) $($params)*)
    };
}

playground

Upvotes: 4

Related Questions