Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust macro accepting argument with a colon, a struct which is inside a module

The following code works:

pub struct Bar {
    pub name: String
}

macro_rules! printme {
    ($myclass: ident) => {
        let t = $myclass { name: "abc".to_owned() };
        println!("{}", t.name);
    }
}

fn main() {
    printme!(Bar);
}

However, if Bar is within a module, it won't work, error is no rules expected the token :::

mod foo {
    pub struct Bar {
        pub name: String
    }
}

macro_rules! printme {
    ($myclass: ident) => {
        let t = $myclass { name: "abc".to_owned() };
        println!("{}", t.name);
    }
}

fn main() {
    printme!(foo::Bar); // not allowed
}

It only works if I use an alias:

fn main() {
    use foo::Bar as no_colon;
    printme!(no_colon);
}

Is there a way to make it work with the colon, without the use alias?

like image 696
rodrigocfd Avatar asked Oct 25 '25 01:10

rodrigocfd


1 Answers

When you write ($myclass: ident) you are saying that the user must write an identifier in that place of the macro invocation. And as you noted, Bar is an identifier, but foo::Bar is not: syntactically, this kind of list-of-identifiers-separated-by-double-colon is called a path.

You can write ($myclass: path), or if you want to limit that to existing types then you can write ($myclass: ty), as @phimuemue's answer suggested. But if you do this, it will fail when trying to use that type to build the object. That is because of how the parser work: it must parse the path and the { in the same token tree, but having the path or ty has broken the link with the {. Since this is just a parser limitation, not a semantic one, you can use a local alias as a workaround, as the other answer suggests.

However, I would suggest to use a trait based solution if possible. I consider that to me much more idiomatic:

trait Nameable {
    fn new(name: &str) -> Self;
}

mod foo {
    pub struct Bar {
        pub name: String
    }
    impl super::Nameable for Bar {
        fn new(name: &str) -> Bar {
            Bar {
                name: name.to_string()
            }
        }
    }
}

macro_rules! printme {
    ($myclass: ty) => {
        let t = <$myclass as Nameable>::new("abc");
        println!("{}", t.name);
    }
}

fn main() {
    printme!( foo::Bar );
}

Or you can take out the ultimate tool of Rust macros: the list-of-token-trees, that can parse almost anything:

macro_rules! printme {
    ($($myclass: tt)*) => {
        let t = $($myclass)* { name: "abc".to_string() };
        println!("{}", t.name);
    }
}

When you invoke this macro with printme!(foo::Bar) it will actually parse as a list of three token-trees: foo, :: and Bar, and then your building of the object will just work.

The drawback (or advantage) of this method is that it will eat up all your tokens, no matter what you write into the macro, and if it fails it will emit a weird error message from inside your macro, instead of saying that your token is not valid in this macro invocation.

For example, writing printme!( foo::Bar {} ) with my trait-based macro gives the most helpful error:

error: no rules expected the token `{`
  --> src/main.rs:27:24
   |
19 | macro_rules! printme {
   | -------------------- when calling this macro
...
27 |     printme!( foo::Bar {} );
   |                        ^ no rules expected this token in macro call

While writing the same code with the token-tree-list macro produces a few not so helpful messages:

warning: expected `;`, found `{`
  --> src/main.rs:21:30
   |
21 |         let t = $($myclass)* { name: "abc".to_string() };
   |                              ^
...
27 |     printme!( foo::Bar {} );
   |     ------------------------ in this macro invocation
   |
   = note: this was erroneously allowed and will become a hard error in a future release

error: expected type, found `"abc"`
  --> src/main.rs:21:38
   |
21 |         let t = $($myclass)* { name: "abc".to_string() };
   |                                    - ^^^^^ expected type
   |                                    |
   |                                    tried to parse a type due to this
...
27 |     printme!( foo::Bar {} );
   |     ------------------------ in this macro invocation

error[E0063]: missing field `name` in initializer of `foo::Bar`
  --> src/main.rs:27:15
   |
27 |     printme!( foo::Bar {} );
   |               ^^^^^^^^ missing `name`
like image 100
rodrigo Avatar answered Oct 26 '25 21:10

rodrigo