rust 为什么“-x”尝试解析为文本,但在声明性宏中失败?

0md85ypi  于 2023-01-09  发布在  其他
关注(0)|答案(2)|浏览(109)

我写了一个小宏来帮助我探索事物被解析为什么。砍掉几条胳膊后,它看起来像:

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_meliteral分支,或者只是将其移到匹配器的底部,其他分支会对此非常满意:

show_me!(-x);
# - is a - and then I see x
# x is an ident and then I see

我的期望是匹配的arm要么匹配成功,要么失败到下一个模式。相反,它抛出了编译错误。在这种情况下发生了什么?
还有,我是读错了文档还是它不正确?对我来说,它似乎明确地说-永远不应该是声明性宏中文字表达式的一部分(尽管它可能是过程性宏的一部分)

s8vozzvw

s8vozzvw1#

这是在issue #82968中询问的,响应为:
这是按预期工作的,非终结元变量总是完全致力于解析语法的一部分,如果失败,整个宏调用将被拒绝。
例如,您可以看到expr具有相同的行为。

1szpjjfi

1szpjjfi2#

除了公认的答案之外,我还了解到,这样做的原因是Rust的未来版本可能支持当前版本不支持的literal s(或expr s,或其他),这可能会导致代码崩溃,例如,假设在Rust的某个版本中,-3/2不是一个字面量,匹配只是忽略了它,那么您可以有:

macro_rules! some_macro{
  ($l : literal) => {std::secret_methods::fire_every_nuclear_warhead()};
  ($($tail : tt)*) => {std::secret_methods::pet_a_puppy()`}
}

然后,对some_macro(-3/2)的调用将安全地解决......但是,当另一个版本的Rust出现时,添加了对文本表达式的本地支持,突然有人的生产代码将导致严重的问题。
相反,当第一个分支无法匹配时,编译时就会崩溃,这样,目前的法律的代码在未来被破坏的可能性就小得多。

相关问题