定义将参数传递给函数的宏

Defining a macro that passes params to a function

这是 的跟进。我正在尝试编写一个宏 call!,它将参数作为一个向量并将它们传递给一个函数。例如,

fn add(x:u32, y:u32) -> u32 { x + y }
let params: [u32; 2] = [2 ,3];
assert_eq!(call!(&add, params), 5); 

对于静态数量的参数,宏很简单:

macro_rules! call {    
    ($function:expr, $params:expr) => {
        $function($params[0], $params[1])
    };
}

我不知道如何让它适用于可变数量的参数。我已经尝试了一些东西,但它们都 运行 变成了两个问题之一:

  1. 我尝试在宏之间传递 $params[0], 等不完整的代码片段,这是不允许的。
  2. 我尝试匹配长度并得到 expected <x> parameters 个错误。

宏的问题是它们没有任何类型信息,您需要知道函数有多少参数,或者数组有多长(并假设两者相等)。因此,如果您想纯粹使用宏来执行此操作,则必须明确提供该信息,例如call!(&add, params, 2).

但是,您可以使用特征来解决这个问题,因为它们 具有类型信息。您可以创建特征 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;
}

然后你可以为你需要的每个参数数量实现它。不幸的是,每个数字都需要自己的实现,所以会有点冗长。

// 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)
    }
}

并且,假设您在作用域中具有特征,您现在可以在任何采用相同参数的函数上调用它:

fn add(x:u32, y:u32) -> u32 { x + y }
let params: [u32; 2] = [2 ,3];
assert_eq!(add.call_expand_args(params), 5);

你也可以基于此实现你想要的宏,如果你真的想要的话,虽然我不确定它会增加更多的价值:

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

此方法的另一个优点是您还可以为数组以外的类型实现它,例如元组:

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

嗯,这是一个可能的解决方案。不是最漂亮的,但它可以工作。基本上,在宏调用时,我们会发送一些宏可以处理的参数并推断出正确的函数调用。

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