Rust 宏接受带冒号的参数,一个位于模块内部的结构
Rust macro accepting argument with a colon, a struct which is inside a module
以下代码有效:
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
在一个模块中,它将不起作用,错误是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
}
只有在我使用别名时才有效:
fn main() {
use foo::Bar as no_colon;
printme!(no_colon);
}
有没有办法让它在 中使用 冒号,而不使用 use
别名?
只需一点小技巧,您就可以让它发挥作用:
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
不是:在语法上,这种 list-of-identifiers-separated-by-double-colon 被称为 path.
你可以写 ($myclass: path)
,或者如果你想将其限制为现有类型,那么你可以写 ($myclass: ty)
,正如@phimuemue 的回答所建议的那样。但是,如果您这样做,则在尝试使用该类型构建对象时将会失败。这是因为解析器的工作方式:它必须在同一个标记树中解析路径和 {
,但是 path
或 ty
已经破坏了 link {
。由于这只是一个解析器限制,而不是语义限制,您可以使用本地别名作为解决方法,正如其他答案所建议的那样。
但是,我建议尽可能使用基于特征的解决方案。我认为这对我来说更加地道:
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 );
}
或者你可以取出 Rust 宏的终极工具:list-of-token-trees,它几乎可以解析任何东西:
macro_rules! printme {
($($myclass: tt)*) => {
let t = $($myclass)* { name: "abc".to_string() };
println!("{}", t.name);
}
}
当您使用 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
在使用 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`
以下代码有效:
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
在一个模块中,它将不起作用,错误是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
}
只有在我使用别名时才有效:
fn main() {
use foo::Bar as no_colon;
printme!(no_colon);
}
有没有办法让它在 中使用 冒号,而不使用 use
别名?
只需一点小技巧,您就可以让它发挥作用:
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
不是:在语法上,这种 list-of-identifiers-separated-by-double-colon 被称为 path.
你可以写 ($myclass: path)
,或者如果你想将其限制为现有类型,那么你可以写 ($myclass: ty)
,正如@phimuemue 的回答所建议的那样。但是,如果您这样做,则在尝试使用该类型构建对象时将会失败。这是因为解析器的工作方式:它必须在同一个标记树中解析路径和 {
,但是 path
或 ty
已经破坏了 link {
。由于这只是一个解析器限制,而不是语义限制,您可以使用本地别名作为解决方法,正如其他答案所建议的那样。
但是,我建议尽可能使用基于特征的解决方案。我认为这对我来说更加地道:
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 );
}
或者你可以取出 Rust 宏的终极工具:list-of-token-trees,它几乎可以解析任何东西:
macro_rules! printme {
($($myclass: tt)*) => {
let t = $($myclass)* { name: "abc".to_string() };
println!("{}", t.name);
}
}
当您使用 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
在使用 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`