有没有办法将参数与 Rust 宏匹配?
Is there a way to match the parameters to a Rust macro?
考虑以下代码:
trait Trait {
fn x(&self) -> u32;
}
struct A {}
impl Trait for A {
fn x(&self) -> u32 {
10
}
}
struct B {}
impl Trait for B {
fn x(&self) -> u32 {
20
}
}
struct C {
created_time: u64,
}
impl Trait for C {
fn x(&self) -> u32 {
30
}
}
impl C {
pub fn new() -> C {
C { created_time: 1000 } // for simplicity
}
}
macro_rules! create {
($type:ident) => {
match stringify!($type) {
"C" => Box::new(C::new()) as Box<dyn Trait>,
_ => Box::new($type {}) as Box<dyn Trait>,
}
};
}
fn main() {
let a: Box<dyn Trait> = create!(A);
let b: Box<dyn Trait> = create!(B);
let c: Box<dyn Trait> = create!(C);
assert_eq!(a.x(), 10);
assert_eq!(b.x(), 20);
assert_eq!(c.x(), 30);
}
如果您要求编译器展开宏,则解析为:
let a: Box<dyn T> =
match "A" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(A{}) as Box<dyn T>,
};
let b: Box<dyn T> =
match "B" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(B{}) as Box<dyn T>,
};
let c: Box<dyn T> =
match "C" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(C{}) as Box<dyn T>,
};
这很好地解释了为什么编译器在尝试编译时会出现以下错误:
error[E0063]: missing field `created_time` in initializer of `C`
--> mwe.rs:29:27
|
29 | _ => Box::new($type { }) as Box<dyn T>,
| ^^^^^ missing `created_time`
...
37 | let c: Box<dyn T> = create!(C);
| ---------- in this macro invocation
error: aborting due to previous error
但是,我原以为编译器会注意到 match "C" { "C" => ..., _ => ... }
的情况并删除第二个子句,因为无论如何它都不可能是 运行。遗憾的是它没有,而是抱怨第二个(不可能的)子句无法编译。
我也尝试用宏中的 if
替换 match
如下,但无济于事:
macro_rules! create {
($type:ident) => {
if stringify!($type) == "C" {
Box::new(C::new()) as Box<dyn T>
} else {
Box::new($type { }) as Box<dyn T>
}
}
}
导致
let c: Box<dyn T> =
if "C" == "C" { Box::new(C::new()) as Box<dyn T> }
else { Box::new(C{}) as Box<dyn T> };
出现与 match
尝试相同的错误。
希望 Haskell 的保护管道语法能以某种方式在 Rust 中工作,我最后还尝试了以下方法:
macro_rules! create {
($type:ident) | (stringify!($type) == "C") => {
Box::new(C::new()) as Box<dyn T>
},
($type:ident) | (stringify!($type) != "C") => {
Box::new($type { }) as Box<dyn T>
},
}
但这给出了 error: no rules expected the token '|'
这最终让我回到了标题中的问题:
有没有办法在宏规则中添加"guards"来告诉编译器"Only run A if this parameter is passed, or run B on something else"?
虽然看起来您的问题确实是 X/Y 问题,并且可以使用 Default
等特征更优雅地解决,但可以在某种程度上匹配宏参数。
您的宏可以重写为
macro_rules! create {
(C) => {
Box::new(C::new()) as Box<dyn Trait>
};
($type:ident) => {
Box::new($type {}) as Box<dyn Trait>
};
}
编译器在第一次成功匹配时停止。
请注意,这有一些限制:如您所料,编译器对标记进行字面比较,例如,以下内容将失败:
type D = C;
let really_just_another_c: Box<dyn Trait> = create!(D);
与
error[E0063]: missing field `created_time` in initializer of `C`
--> src/main.rs:41:18
|
41 | Box::new($type {}) as Box<dyn Trait>
| ^^^^^ missing `created_time`
...
51 | let c: Box<dyn Trait> = create!(D);
| ---------- in this macro invocation
考虑以下代码:
trait Trait {
fn x(&self) -> u32;
}
struct A {}
impl Trait for A {
fn x(&self) -> u32 {
10
}
}
struct B {}
impl Trait for B {
fn x(&self) -> u32 {
20
}
}
struct C {
created_time: u64,
}
impl Trait for C {
fn x(&self) -> u32 {
30
}
}
impl C {
pub fn new() -> C {
C { created_time: 1000 } // for simplicity
}
}
macro_rules! create {
($type:ident) => {
match stringify!($type) {
"C" => Box::new(C::new()) as Box<dyn Trait>,
_ => Box::new($type {}) as Box<dyn Trait>,
}
};
}
fn main() {
let a: Box<dyn Trait> = create!(A);
let b: Box<dyn Trait> = create!(B);
let c: Box<dyn Trait> = create!(C);
assert_eq!(a.x(), 10);
assert_eq!(b.x(), 20);
assert_eq!(c.x(), 30);
}
如果您要求编译器展开宏,则解析为:
let a: Box<dyn T> =
match "A" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(A{}) as Box<dyn T>,
};
let b: Box<dyn T> =
match "B" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(B{}) as Box<dyn T>,
};
let c: Box<dyn T> =
match "C" {
"C" => Box::new(C::new()) as Box<dyn T>,
_ => Box::new(C{}) as Box<dyn T>,
};
这很好地解释了为什么编译器在尝试编译时会出现以下错误:
error[E0063]: missing field `created_time` in initializer of `C`
--> mwe.rs:29:27
|
29 | _ => Box::new($type { }) as Box<dyn T>,
| ^^^^^ missing `created_time`
...
37 | let c: Box<dyn T> = create!(C);
| ---------- in this macro invocation
error: aborting due to previous error
但是,我原以为编译器会注意到 match "C" { "C" => ..., _ => ... }
的情况并删除第二个子句,因为无论如何它都不可能是 运行。遗憾的是它没有,而是抱怨第二个(不可能的)子句无法编译。
我也尝试用宏中的 if
替换 match
如下,但无济于事:
macro_rules! create {
($type:ident) => {
if stringify!($type) == "C" {
Box::new(C::new()) as Box<dyn T>
} else {
Box::new($type { }) as Box<dyn T>
}
}
}
导致
let c: Box<dyn T> =
if "C" == "C" { Box::new(C::new()) as Box<dyn T> }
else { Box::new(C{}) as Box<dyn T> };
出现与 match
尝试相同的错误。
希望 Haskell 的保护管道语法能以某种方式在 Rust 中工作,我最后还尝试了以下方法:
macro_rules! create {
($type:ident) | (stringify!($type) == "C") => {
Box::new(C::new()) as Box<dyn T>
},
($type:ident) | (stringify!($type) != "C") => {
Box::new($type { }) as Box<dyn T>
},
}
但这给出了 error: no rules expected the token '|'
这最终让我回到了标题中的问题:
有没有办法在宏规则中添加"guards"来告诉编译器"Only run A if this parameter is passed, or run B on something else"?
虽然看起来您的问题确实是 X/Y 问题,并且可以使用 Default
等特征更优雅地解决,但可以在某种程度上匹配宏参数。
您的宏可以重写为
macro_rules! create {
(C) => {
Box::new(C::new()) as Box<dyn Trait>
};
($type:ident) => {
Box::new($type {}) as Box<dyn Trait>
};
}
编译器在第一次成功匹配时停止。
请注意,这有一些限制:如您所料,编译器对标记进行字面比较,例如,以下内容将失败:
type D = C;
let really_just_another_c: Box<dyn Trait> = create!(D);
与
error[E0063]: missing field `created_time` in initializer of `C`
--> src/main.rs:41:18
|
41 | Box::new($type {}) as Box<dyn Trait>
| ^^^^^ missing `created_time`
...
51 | let c: Box<dyn Trait> = create!(D);
| ---------- in this macro invocation