porgarmingduod
porgarmingduod

Reputation: 7878

Macro matching a single expr after [expr*], path[tt*] and ident[tt*] branches

I'm trying to make a macro that I can call in the following manner:

mactest!(some::Path[1, 2, AnotherName[3, 4]])

Which would be equivalent to the following:

make_result(
    "some::Path", 
    1.convert(), 
    2.convert(), 
    make_result(
        "AnotherName", 
        3.convert(), 
        4.convert()
    )
)

where convert is some trait that will be implemented for a bunch of types. (convert and make_result has the same result type).

This is as far as I've come:

// Note: u32 is used as an example result type. 
// The real code attempts to create a more complicated object.

trait Foo {
    fn convert(&self) -> u32;
}

fn make_result(name: &str, data: Vec<u32>) -> u32 {
    // This example ignores name and makes a meaningless result
    data.iter().fold(0,|a, &b| a + b)
}


#[macro_export]
macro_rules! mactest {
    ( [ $($inner:expr),* ] ) => {{
        let mut result = Vec::new();
        $(
            // Process each element.
            result.push(mactest!($inner));
        )*
        result
    }};
    ($name:path [ $($inner:tt),* ] ) => {
        make_result(stringify!($name), mactest!([$($inner),*]))
    };
    ($name:ident [ $($inner:tt),* ] ) => {
        make_result(stringify!($name), mactest!([$($inner),*]))
    };
    // Process single value. This is never matched?
    ($x:expr) => {
        $x.convert()
    };
}

The first matching branch of the macro is supposed to match each element of a list to either the path/ident[items] or the single item .convert branch at the end. But the final branch is never reached, with rust complaining error: expected ident, found '1' when single items enter the macro, i.e. mactest!(1).

My reasoning as a beginner rust user is that the macro has four patterns: [expr*], path[tt*], ident[tt*] and expr. When I pass something like 1 into the macro, I don't see why any of the above patterns should match/interfere.

Can someone explain why this doesn't work? Is there a workaround to get the intended result?

Upvotes: 0

Views: 1504

Answers (1)

oli_obk
oli_obk

Reputation: 31183

macro rules are tried by starting with the first one and going down from there. So if you want to prevent your other rules from triggering in special cases, you need to put the special case rule first.

Try it out in the playground

Upvotes: 0

Related Questions