20chan
20chan

Reputation: 177

Recursive macro makes infinite recursion

I made a simple macro that returns the taken parameter.

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

When I compile this code with the option external-macro-backtrace, it raises an error:

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

I changed the recursion_limit to 128 and higher, but the compiler error message just increase as well. Even when I call n!(0) it makes the same error. I think it is infinite recursion, but I can't find the reason.

Upvotes: 1

Views: 890

Answers (1)

Cerberus
Cerberus

Reputation: 10247

Well, it really is an infinite recursion. Check what your macro invocation n!(0) will be expanded into:

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

...and since there's no way for argument of n! to stop growing negative, it'll repeat (with n!(0 - 1 - 1) in the second match arm, then n!(0 - 1 - 1 - 1) etc.) infinitely.

The key point here is that the macro expansion happens in compile-time, while the match statement you're trying to use to limit the recursion is invoked only at run-time and can't stop anything from appear before that. Unhappily, there's no easy way to do this, since Rust won't evaluate macro arguments (even if it's a constant expression), and so just adding the (0) => {0} branch to the macro won't work, since the macro will be invoked as (for example) n!(1 - 1).

Upvotes: 2

Related Questions