Рекурсивный макрос делает бесконечную рекурсию

Я сделал простой макрос, который возвращает взятый параметр.

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), он делает ту же ошибку. Я думаю, что это бесконечная рекурсия, но не могу найти причину.

По сути, дубликат Есть ли способ посчитать с помощью макросов?.

Shepmaster 26.10.2018 17:10
1
1
525
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Что ж, это действительно бесконечная рекурсия. Проверьте, во что будет расширен ваш вызов макроса 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).

Другие вопросы по теме