Search code examples
rustrust-macros

expected one of `!` or `::`, found _ - rust macro with items


I am trying to implement a rust macro with items. Both Osc and Multiply implement a trait with const id: [char; 3] and fn new(cctx: &CreationalContext) -> Self

The code is:

macro_rules! ifid {
    (($id:expr, $ctx:expr) $($node:item),+) => {
        {
            $(
                if ($id == $node::id) {
                    return $node::new($ctx)
                }
            )+
        }
    }
}

pub fn id_to_node(id: [char; 3], context: CreationalContext) -> impl Process {
    ifid! {
        (id, context)
        osc::Osc,
        multiply::Multiply,
    }
}

and the error is:

expected one of `!` or `::`, found `,`
expected one of `!` or `::`rustc

If I remove the commas from the macro, a similar error occurs.

P.S. I know you probably can't return from a macro; I tried to assign the new struct instance to a variable, but variables cannot implement traits: let it: &impl Process; errors.

Context: I want to match a three char id sent from js to a new instance of a struct that implies a trait called Process. I tried putting all the constructors in a vector, but you can only use impl in a function return type, not in the return type of a pointer to a function.


Solution

  • Minimized self-contained example (playground):

    macro_rules! ifid {
        (($id:expr, $ctx:expr) $($node:item),+) => {}
    }
    
    pub fn id_to_node(id: (), context: ()) {
        ifid! {
            (id, context)
            osc::Osc,
        }
    }
    

    This code has two independent problems, in fact, but the second is much easier - macro as shown doesn't allow for trailing commas; I'll show how this can be fixed in the end.

    The main error is with the fragment specifier used - namely, item. Quoting the documentation:

    The item fragment simply matches any of Rust's item definitions, not identifiers that refer to items. This includes visibility modifiers.

    That is, it could be used for something like struct definition, function, module declaration - everything that can be used at the top-level of the Rust module.

    What do you seem to want instead is path or ty. With this change and the fix for the trailing comma, macro will look like this:

    macro_rules! ifid {
        (($id:expr, $ctx:expr) $($node:path),+ $(,)?) => {}
    }
    

    Of course, you should fill in your own logic there, but now it should work, unless you want something radically different from the code shown in the question.