2014-07-01 9 views
6

Próbuję utworzyć makro w Rust, który pozwala mi napisaćJak używać makr variadic do wywoływania konstruktorów zagnieżdżonych?

make_list!(1, 2, 3) 

zamiast

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

które powinny pracować dla dowolnej liczby „Parametry” w tym zero. To, co mam tak daleko:

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

ale pojawia się następujący błąd:

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

nie mogę sensu tego. Z tego co wiem, powinno działać. Co zrobiłem źle?

Kompletny kod:

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

Twoje makro przyjmuje 0 lub 2 argumenty, ale przekazujesz je tylko 1 – Arjan

+1

@Arjan: Ale myślałem, że to jest to, za co $ (...), * jest. Powinien pasować zero do większej liczby argumentów. Czy nie powinien? – sellibitze

Odpowiedz

8

Rozszerzając błędu: dostać się do przypadku, w którym istnieje tylko jedna wartość, i tak pisze to make_list!(1). Nie ma jednak reguły, która by to pasowała, ponieważ druga reguła, po użyciu wyrażenia x, chce przecinka, którego nie ma.

Więc trzeba zrobić to tak, że będzie pracować dla make_list!(1) i nie tylko (w rzeczywistości tylko nie) make_list!(1,). Aby to osiągnąć, uzyskać przecinek wewnątrz powtarzającej się części, jak poniżej:

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

Bonus: można napisać make_list![1, 2, 3] zamiast make_list!(1, 2, 3) jeśli chcesz.

+0

Dzięki! Sądzę, że jestem zbyt przyzwyczajony do magii szablonów variadycznych w C++. :) – sellibitze

+1

To podejście już nie działa; zobacz [tutaj] (https://users.rust-lang.org/t/tail-recursive-macros/905/3). – bgilbert

+0

Jaka część tej odpowiedzi nie działa? – ideasman42

0

Jak zauważono w odpowiedzi @ chris-morgan, rozszerzenie pojedynczego argumentu nie jest uwzględniane.

Więc można albo to przecinek w ekspansji lub dodać pojedynczy przypadek w makro:

przykładzie oba, jeden argument:

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

Łącznie z przecinkiem w ekspansji:

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

Oto przykładowe pełni funkcjonalny oparty na pytaniu i zaktualizowane do 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); 
} 
Powiązane problemy