我写了一个小宏来帮助我探索事物被解析为什么。砍掉几条胳膊后,它看起来像:
macro_rules! show_me{
($l : literal $($tail : tt)*) => {
println!("{} is a literal and then I see {}", stringify!($l), stringify!($($tail)*));
show_me!($($tail)*)
};
(- $($tail : tt)*) => {
println!("{} is a - and then I see {}", stringify!(-), stringify!($($tail)*));
show_me!($($tail)*)
};
($i : ident $($tail : tt)*) => {
println!("{} is an ident and then I see {}", stringify!($i), stringify!($($tail)*));
show_me!($($tail)*)
};
($token : tt $($tail : tt)*) => {
println!("{} is a mystery and then I see {}", stringify!($token), stringify!($($tail)*));
show_me!($($tail)*)
};
() => {}
}
使用rustc 1.66.0(69 f9 c33 d 7 2022-12-12)时,在某些输入上的表现与预期一致:
show_me!(x + 1);
# x is an ident and then I see + 1
# + is a mystery and then I see 1
# 1 is a literal and then I see
但如果我尝试一个负数,我会(有点)惊讶:
show_me!(-4);
# -4 is a literal and then I see
这是非常有意义的,除了https://doc.rust-lang.org/reference/procedural-macros.html说(在声明性宏的描述下):
- 文字(“字符串”、1等)
请注意,否定(例如-1)从来不是此类文字标记的一部分,而是单独的运算符标记。
让我感到奇怪的是,如果我尝试运行show_me!(-x)
:
error: unexpected token: `x`
--> src/main.rs:54:15
|
54 | show_me!(-x);
| ^
|
::: src/utils/macros.rs:27:6
|
27 | ($l : literal $($tail : tt)*) => {
| ------------ while parsing argument for this `literal` macro fragment
请注意,如果我注解掉show_me
的literal
分支,或者只是将其移到匹配器的底部,其他分支会对此非常满意:
show_me!(-x);
# - is a - and then I see x
# x is an ident and then I see
我的期望是匹配的arm要么匹配成功,要么失败到下一个模式。相反,它抛出了编译错误。在这种情况下发生了什么?
还有,我是读错了文档还是它不正确?对我来说,它似乎明确地说-
永远不应该是声明性宏中文字表达式的一部分(尽管它可能是过程性宏的一部分)
2条答案
按热度按时间s8vozzvw1#
这是在issue #82968中询问的,响应为:
这是按预期工作的,非终结元变量总是完全致力于解析语法的一部分,如果失败,整个宏调用将被拒绝。
例如,您可以看到
expr
具有相同的行为。1szpjjfi2#
除了公认的答案之外,我还了解到,这样做的原因是Rust的未来版本可能支持当前版本不支持的
literal
s(或expr
s,或其他),这可能会导致代码崩溃,例如,假设在Rust的某个版本中,-3/2
不是一个字面量,匹配只是忽略了它,那么您可以有:然后,对
some_macro(-3/2)
的调用将安全地解决......但是,当另一个版本的Rust出现时,添加了对文本表达式的本地支持,突然有人的生产代码将导致严重的问题。相反,当第一个分支无法匹配时,编译时就会崩溃,这样,目前的法律的代码在未来被破坏的可能性就小得多。