是否有可能在Rust的编译时计算递归函数?

时间:2017-06-09 02:45:08

标签: recursion macros rust const compile-time-constant

我想计算const的阶乘。我想最终得到这样的东西:

const N: usize = 4;
const N_PERMUTATIONS = factorial(N);

明显的解决方案(不起作用)是:

  • const fn - const fn中不允许(或至少未实现)条件语句,以下都不会编译:

    const fn factorial(n: usize) -> usize {
        match n {
            0 => 1,
            _ => n * factorial(n-1)
        }
    }
    
    const fn factorial(n: usize) -> usize {
        if n == 0 {
            1
        } else {
            n * factorial(n-1)
        }
    }
    
  • 宏 - 在所有宏扩展之后执行表达式的评估。此宏永远不会达到基本情况,因为在四次迭代后,参数为4-1-1-1-1,与0不匹配:

    macro_rules!factorial {
        (0) => (1);
        ($n:expr) => ($n * factorial($n-1));
    }
    

我还尝试了以下内容,如果*进行了短路评估,它会起作用,但是as-is有无条件的递归会导致堆栈溢出:

const fn factorial(n: usize) -> usize {
    ((n == 0) as usize) + ((n != 0) as usize) * n * factorial(n-1)
}

(正如Matthieu M.指出的那样,我们可以通过factorial(n - ((n != 0) as usize))来避免下溢。)

现在我已经使用手动计算阶乘。

2 个答案:

答案 0 :(得分:3)

This is currently explored under the feature const_fn, but for now you cannot call a function, even const, from another const function.

You can however break out the big guns: metaprogramming (procedural macro) to compute the value at compile-time. I found this crate for example (but did not test it, though).

This Rosetta Code page on compile time calculation shows that the compiler can do some compile-time optimization, but nothing is guaranteed, and this is only a particular case.

答案 1 :(得分:2)

自您的原始问题以来,Rust已更新,现在支持const fn中的条件,因此前两个解决方案都可以使用。参见the docs 指出您可以在const函数中拥有“对其他安全const函数的调用(无论是通过函数调用还是方法调用)”。

对于您的特定阶乘示例,您(至少)有几个选择。这是我已经成功编译的阶乘函数:

const fn factorial(n: u64) -> u64 {
    match n {
        0u64 | 1u64 => 1,
        2u64..=20u64 => factorial(n - 1u64) * n,
        _ => 0,
    }
}

注意,n!其中n> 20将使{{​​1}}溢出,因此我决定在这种情况下返回0。另外,由于u64可以是32位值,因此在这种情况下,我明确使用64位usize。处理u64溢出情况还可以防止堆栈溢出。这可能会返回u64

Option<u64>

在我的情况下,返回const fn factorial(n: u64) -> Option<u64> { match n { 0u64 | 1u64 => Some(1), 2u64..=20u64 => match factorial(n - 1u64) { Some(x) => Some(n * x), None => None, }, _ => None, } } 限制了我使用该函数的方式,因此我发现仅返回一个{0}与Option<u64>类似的u64更为有用。 / p>