Reputation: 291
This is a follow up to a question I asked yesterday. I'm trying to write a macro call!
that takes parameters as a vector and passes them to a function. E.g.,
fn add(x:u32, y:u32) -> u32 { x + y }
let params: [u32; 2] = [2 ,3];
assert_eq!(call!(&add, params), 5);
For a static number of parameters, the macro is easy:
macro_rules! call {
($function:expr, $params:expr) => {
$function($params[0], $params[1])
};
}
I can't figure out how to make it work for a variable number of parameters. I have tried a few things, but they all run into one of two problems:
$params[0],
between macros, which is not allowed.expected <x> parameters
errors.Upvotes: 1
Views: 1671
Reputation: 198
Hm, this is a possible solution. Not the prettiest but it can work. Basically, on macro call, we will send a number of parameters that macro can process and deduce proper function call.
macro_rules! call {
($function:expr, 1, $params:expr) => {
$function($params[0])
};
($function:expr, 2, $params:expr) => {
$function($params[0], $params[1])
};
($function:expr, 3, $params:expr) => {
$function($params[0], $params[1], $params[2])
};
}
fn main(){
fn add(x:u32, y:u32) -> u32 { x + y }
fn incr(x:u32) -> u32 { x + 1 }
let params1: [u32; 1] = [2];
let mut params2: [u32; 2] = [2,3];
assert_eq!(call!(&incr, 1, params1), 3);
assert_eq!(call!(&add, 2, params2), 5);
params2[0]=4;
assert_eq!(call!(&add, 2, params2), 7);
}
Upvotes: 0
Reputation: 40934
The problem with macros is that they don't have any type information, and you need to know either how many arguments the function takes, or how long the array is (and assume the two are equal). So if you want to do this purely with macros, you will have to give that information in explicitly, e.g. call!(&add, params, 2)
.
However, you can solve this with traits since they do have type information. You can create a trait FnExpandArgs
:
// Helper trait for calling a function with a list of arguments.
trait FnExpandArgs<Args> {
// Return type.
type Output;
/// Call function with an argument list.
fn call_expand_args(&self, args: Args) -> Self::Output;
}
Then you can implement it for each number of arguments you need. Unfortunately each number needs its own implementation, so it will be a bit verbose.
// Example implementation for 2 arguments
impl<F, T, R> FnExpandArgs<[T; 2]> for F
where
F: Fn(T, T) -> R
{
type Output = R;
fn call_expand_args(&self, args: [T; 2]) -> R {
// Expand array of arguments
let [arg0, arg1] = args;
// Call function
self(arg0, arg1)
}
}
And, assuming you have the trait in scope, you can now call it on any function that takes identical arguments:
fn add(x:u32, y:u32) -> u32 { x + y }
let params: [u32; 2] = [2 ,3];
assert_eq!(add.call_expand_args(params), 5);
You can also implement the macro you wanted based on this, if you really want to, although I'm not sure it adds much more value:
marco_rules! call {
($function:expr, $params:expr) => {
FnExpandArgs::call_expand_args(&$function, $params)
}
}
fn add(x:u32, y:u32) -> u32 { x + y }
let params: [u32; 2] = [2 ,3];
assert_eq!(call!(add, params), 5);
One additional advantage of this apporach is that you can also implement it for types other than arrays, such as tuples:
impl<F, T0, T1, R> FnExpandArgs<(T0, T1)> for F
where
F: Fn(T0, T1) -> R
{
type Output = R;
fn call_expand_args(&self, args: (T0, T1)) -> R {
let (arg0, arg1) = args;
self(arg0, arg1)
}
}
fn shift(x:u32, y:u8) -> u32 { x << y }
let params: (u32, u8) = (2, 3);
assert_eq!(shift.call_expand_args(params), 16);
Upvotes: 1