递归宏进行无限递归

时间:2018-10-26 03:10:56

标签: recursion macros rust rust-cargo

我做了一个简单的宏,返回了参数。

macro_rules! n {
    ($n:expr) => {{
        let val: usize = $n;
        match val {
            0 => 0,
            _ => n!(val - 1),
        }
    }};
}

当我使用选项external-macro-backtrace编译此代码时,会引发错误:

error: recursion limit reached while expanding the macro `n`
  --> src/main.rs:15:18
   |
10 |   macro_rules! n {
   |  _-
   | |_|
   | |
11 | |     ($n:expr) => {{
12 | |         let val: usize = $n;
13 | |         match val {
14 | |             0 => 0,
15 | |             _ => n!(val - 1),
   | |                  ^^^^^^^^^^^
   | |                  |
   | |                  in this macro invocation
16 | |         }
17 | |     }};
18 | | }
   | | -
   | |_|
   | |_in this expansion of `n!`
   |   in this expansion of `n!`
...
31 | |     n!(1);
   | |     ------ in this macro invocation
   |
   = help: consider adding a `#![recursion_limit="128"]` attribute to your crate

我将recursion_limit更改为128或更高,但是编译器错误消息也在增加。即使我打电话给n!(0),也会犯同样的错误。我认为这是无限递归,但我找不到原因。

1 个答案:

答案 0 :(得分:2)

嗯,这确实是一个无限递归。检查您的宏调用n!(0)将被扩展为:

{
    let val: usize = 0;
    match val {
        0 => 0,
        _ => n!(0 - 1),
    }
}

...并且由于无法使n!的参数停止增长为负数,因此它将重复(在第二个比赛分支中使用n!(0 - 1 - 1),然后是n!(0 - 1 - 1 - 1)等)。无限地。

这里的关键点是宏扩展发生在编译时,而您试图用来限制递归的match语句仅在运行时被调用,并且不能从在此之前出现。不幸的是,没有简单的方法可以做到这一点,因为Rust不会评估宏参数(即使它是一个常量表达式),因此仅将(0) => {0}分支添加到宏是行不通的,因为宏会被调用为n!(1 - 1)