Rust宏接受带有冒号的参数,冒号是模块内部的结构

Rust宏接受带有冒号的参数,冒号是模块内部的结构,rust,macros,arguments,Rust,Macros,Arguments,以下代码起作用: 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); } 但是,如果Bar在一个模块内,它将不起

以下代码起作用:

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);
}
但是,如果
Bar
在一个模块内,它将不起作用,错误是
不需要任何规则令牌::

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
}
仅当我使用别名时它才起作用:

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

有没有一种方法可以让它使用冒号,而不使用
别名?

通过一些小技巧,您可以让它工作:

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

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

fn main() {
    printme!(foo::Bar);
}
  • 接受
    ty
    (类型)而不是
    ident
    (标识符)
  • 我不知道为什么,但是如果没有
    LocalT
当您编写
($myclass:ident)
时,您的意思是用户必须在宏调用的那个位置写入标识符。正如您所指出的,
Bar
是一个标识符,但
foo::Bar
不是:从语法上讲,这种由双冒号分隔的标识符列表称为路径

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

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

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 );
}
当您使用
printme!(foo::Bar)
调用此宏时,它实际上将解析为三个标记树的列表:
foo
Bar
,然后您的对象构建就可以正常工作了

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

例如,使用基于特征的宏编写
printme!(foo::Bar{})
会产生最有用的错误:

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
使用令牌树列表宏编写相同的代码时,会产生一些不太有用的消息:

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`
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`