trexinf14s
trexinf14s

Reputation: 291

Defining a macro that passes params to a function

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:

  1. I try to pass incomplete code fragments like $params[0], between macros, which is not allowed.
  2. I try to match length and get expected <x> parameters errors.

Upvotes: 1

Views: 1671

Answers (2)

draganrakita
draganrakita

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

Freyja
Freyja

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);

Playground example

Upvotes: 1

Related Questions