How to use variadic macros to call nested construc

2019-04-28 19:35发布

问题:

I'm trying to create a macro in Rust that lets me write

make_list!(1, 2, 3)

instead of

Node::new(1, Node::new(2, Node::new(3, None)))

which should work for an arbitrary number of "parameters" including zero. This is what I have so far:

macro_rules! make_list(
    () => (
        None
    );
        ( $x:expr, $( $more:expr ),* ) => (
        Node::new($x, make_list!( $( $more ),* ))
    )
);

but I get the following error:

error: unexpected end of macro invocation
  --> src/main.rs:19:42
   |
19 |             Node::new($x, make_list!( $( $more ),* ))
   |                                          ^^^^^

I can't make much sense of this. From what I can tell, it should work. What did I do wrong?

The complete code:

type List<T> = Option<Box<Node<T>>>;

struct Node<T> {
    value: T,
    tail: List<T>,
}

impl<T> Node<T> {
    fn new(val: T, tai: List<T>) -> List<T> {
        Some(Box::new(Node::<T> {
            value: val,
            tail: tai,
        }))
    }
}

macro_rules! make_list(
    () => (
        None
    );
    ( $x:expr, $( $more:expr ),* ) => (
        Node::new($x, make_list!( $( $more ),* ))
    )
);

fn main() {
    let _list: List<i32> = make_list!(1, 2, 3, 4, 5, 6, 7, 8, 9);
}

回答1:

Expanding on the error: you get down to the case where there is only one value, and so it writes make_list!(1). However, there is no rule that will match that, for the second rule, after consuming the expression x, wants a comma, which is not provided.

So you need to make it so that it will work for make_list!(1) and not just (in fact, just not) make_list!(1,). To achieve this, get the comma inside the repeating part, like this:

macro_rules! make_list(
    () => (
        None
    );
    ( $x:expr $( , $more:expr )* ) => (
        Node::new($x, make_list!( $( $more ),* ))
    )
);

Bonus: you can write make_list![1, 2, 3] instead of make_list!(1, 2, 3) if you want.



回答2:

As noted by @chris-morgan's answer, expanding the single argument case isn't accounted for.

So you can either include comma in the expansion, or add a single case in the macro:

Example of both, single argument:

macro_rules! make_list {
    () => (
        None
    );
    ($x:expr) => (
        Node::new($x, None)
    );
    ($x:expr, $($more:expr),+) => (
        Node::new($x, make_list!($($more),*))
    );
}

Including the comma in the expansion:

macro_rules! make_list {
    () => (
        None
    );
    ($x:expr $(, $more:expr)*) => (
        Node::new($x, make_list!($($more),*))
    );
}

Here is a fully working example based on the question and updated for Rust 1.14:

type List<T> = Option<Box<Node<T>>>;

#[derive(Debug)]
struct Node<T> {
    value: T,
    tail: List<T>
}

impl<T> Node<T> {
    fn new(val: T, tai: List<T>) -> List<T> {
        Some(Box::new(Node::<T> { value: val, tail: tai }))
    }
}

macro_rules! make_list {
    () => (
        None
    );
    ($x:expr $(, $more:expr)*) => (
        Node::new($x, make_list!($($more),*))
    );
}

fn main() {
    let list: List<i64> = make_list!();
    println!("{:?}", list);
    let list: List<i64> = make_list!(1);
    println!("{:?}", list);
    let list: List<i64> = make_list!(1,2,3,4,5,6,7,8,9);
    println!("{:?}", list);
}


标签: macros rust