Определение макроса, который передает параметры в функцию - PullRequest
0 голосов
/ 21 февраля 2020

Это продолжение до вопроса, который я задал вчера . Я пытаюсь написать макрос call!, который принимает параметры как вектор и передает их в функцию. Например,

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

Для количества параметров c макрос прост:

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

Я не могу понять, как заставить его работать с переменным числом параметров. Я пробовал несколько вещей, но все они сталкиваются с одной из двух проблем:

  1. Я пытаюсь передать неполные фрагменты кода, такие как $params[0],, между макросами, что недопустимо.
  2. Я пытаюсь сопоставить длину и получаю expected <x> parameters ошибок.

Ответы [ 2 ]

1 голос
/ 22 февраля 2020

Проблема с макросами заключается в том, что они не имеют никакой информации о типе, и вам необходимо знать, сколько аргументов принимает функция, или какова длина массива (и предположим, что они равны). Поэтому, если вы хотите сделать это исключительно с помощью макросов, вам придется явно указать эту информацию, например, call!(&add, params, 2).

Однако вы можете решить эту проблему с помощью признаков, поскольку они do имеют введите информацию. Вы можете создать черту 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);

Пример игровой площадки

0 голосов
/ 22 февраля 2020

Хм, это возможное решение. Не самая красивая, но она может работать. По сути, при вызове макроса мы отправим ряд параметров, которые макрос может обработать, и определим правильный вызов функции.

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