Attemps to do recursion with const generic params

Hi I am playing the following code on the rust playground. This code is one of my attempt to use const generic param to calculate factorial by recursion.

#![feature(generic_const_exprs)]



struct FactImpl<const N: usize>();

impl<const N: usize> FactImpl<N>
where [(); N-1]:
{
    const fn fact(self: Self) -> usize {
        let f: FactImpl<{N-1}> = FactImpl;
        f.fact() * N
    }
}

impl<> FactImpl<0> {
    const fn fact(self: Self) -> usize {
        1
    }
}


fn main() {
    let f: FactImpl<0> = FactImpl;
    println!("{}", f.fact());
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
 --> src/main.rs:1:12
  |
1 | #![feature(generic_const_exprs)]
  |            ^^^^^^^^^^^^^^^^^^^
  |
  = note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information
  = note: `#[warn(incomplete_features)]` on by default

error[E0080]: evaluation of `FactImpl::<0>::{constant#0}` failed
 --> src/main.rs:8:12
  |
8 | where [(); N-1]:
  |            ^^^ attempt to compute `0_usize - 1_usize`, which would overflow

For more information about this error, try `rustc --explain E0080`.
warning: `playground` (bin "playground") generated 1 warning
error: could not compile `playground` (bin "playground") due to 1 previous error; 1 warning emitted

In the code above I want to calculate the factorial using const generic param and generic recursion. I have tried to change the order of two impl blocks, remove the first impl block that is with generic param. When I remove the first impl block and leave only impl<> FactImpl<0> the code compiles.

My questions are:

  1. What is the reason for the compile error? Is it a feature of bug?
  2. In C++ we have SFINAE, does rust employ SFINAE in type inference? If no, could the code pass compilation if rust have SFINAE?
  3. How to fix this code in rust version 2021?

No.

If my grandmother had wheels she would have been a bike.

And you are hitting corner cases where decision not to go C++-like SFINAE route prevents certain features from being stabilized.

Indeed, if you want to avoid problems that C++ got as a result of it's decision to allow conflicts in declarations (if you don't allow conflicts then SFINAE is pointless) then certain other parts become hard. And Rust's decision to ensure TMP is impossible certainly has it's drawbacks, but it's one of cornerstones of Rust type system.