Rust 宏接受带冒号的参数,这是一个模块内的结构

Rod*_*igo 2 macros arguments rust

以下代码有效:

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);
}
Run Code Online (Sandbox Code Playgroud)

但是,如果Bar在一个模块内,它将不起作用,错误是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
}
Run Code Online (Sandbox Code Playgroud)

仅当我使用别名时才有效:

fn main() {
    use foo::Bar as no_colon;
    printme!(no_colon);
}
Run Code Online (Sandbox Code Playgroud)

有没有办法让它在没有别名的情况下使用冒号use

rod*_*igo 6

当你写的时候,($myclass: ident)你是说用户必须在宏调用的那个地方写一个标识符。正如你所指出的,Bar是一个标识符,但foo::Bar不是:在语法上,这种由双冒号分隔的标识符列表被称为路径

您可以编写($myclass: path),或者如果您想将其限制为现有类型,那么您可以编写($myclass: ty),正如@phimuemue 的回答所建议的那样。但是,如果您这样做,则在尝试使用该类型构建对象时将失败。那是因为解析器的工作方式:它必须解析{同一令牌树中的路径和,但是pathty已经断开了与{. 由于这只是解析器限制,而不是语义限制,因此您可以使用本地别名作为解决方法,正如另一个答案所建议的那样。

但是,如果可能,我建议使用基于特征的解决方案。我认为这对我来说更惯用:

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 );
}
Run Code Online (Sandbox Code Playgroud)

或者你可以拿出 Rust 宏的终极工具:令牌树列表,它几乎可以解析任何东西:

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

当你用printme!(foo::Bar)它调用这个宏时,它实际上会解析为三个标记树的列表:foo,::Bar,然后你的对象构建将正常工作。

这种方法的缺点(或优点)是它会吃掉你所有的令牌,无论你在宏中写什么,如果失败,它会从你的宏内部发出一条奇怪的错误消息,而不是说你的令牌在此宏调用中无效。

例如,printme!( foo::Bar {} )使用我的基于 trait 的宏编写会给出最有用的错误:

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
Run Code Online (Sandbox Code Playgroud)

使用 token-tree-list 宏编写相同的代码会产生一些不太有用的消息:

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`
Run Code Online (Sandbox Code Playgroud)