How to implement with generic_const_expr?

Cannot use generic const parameters in arithmetic operations

impl<T, const R: usize, const C: usize> Matrix<T, R, C>
where
    T: Default + Copy,
{

    pub fn minor_matrix(&self, row: usize, col: usize) -> Matrix<T, { C - 1 }, { R - 1 }> {
        let mut result = Matrix::default();

        let mut new_r = 0;
        for r in 0..R {
            if r == row {
                continue;
            }
            let mut new_c = 0;
            for c in 0..C {
                if c == col {
                    continue;
                }
                result[new_r][new_c] = self[r][c];
                new_c += 1;
            }
            new_r += 1;
        }
        result
    }
}

It works (or seems to work) with the nightly-only generic_const_exprs feature: Rust Playground.

Though do note that there’s a warning: “the feature generic_const_exprs is incomplete and may not be safe to use and/or cause compiler crashes”.

I think there’s 0 chance of something like that using const generics working on stable. There are other ways of encoding numbers in the type system, though.

N-1 as generic param cannot work with recursive determinant

impl<T, const N: usize> Matrix<T, N, N>
where
    T: Default + Copy + Sub<Output = T> + Mul<Output = T> + AddAssign + Neg<Output = T>,
{
    const fn determinant(&self) -> T {
        if N == 1 {
            self[0][0]
        } else if N == 2 {
            self[0][0] * self[1][1] - self[1][0] * self[0][1]
        } else {
            let mut det = T::default();
            for j in 0..N {
                let minor = self.minor_matrix(0, j);
                if j % 2 == 0 {
                    det += self[0][j] * minor.determinant();
                } else {
                    det += -(self[0][j] * minor.determinant());
                } ;
            }
            det
        }
    }
}

Remember to account for N==0; integer underflow might be causing a compile-time error.

Also, a match would be more idiomatic than if / else if on the same variable.


impl<T, const R: usize, const C: usize> Matrix<T, R, C>
where
    T: Default + Copy,
    [(); R - 1]:,
    [(); C - 1]:,
{
    pub fn minor_matrix(&self, row: usize, col: usize) -> Matrix<T, { R - 1 }, { C - 1 }>
    where
    {
        let mut result = Matrix::default();

        let mut new_r = 0;
        for r in 0..R {
            if r == row {
                continue;
            }
            let mut new_c = 0;
            for c in 0..C {
                if c == col {
                    continue;
                }
                result[new_r][new_c] = self[r][c];
                new_c += 1;
            }
            new_r += 1;
        }
        result
    }
}

impl<T, const N: usize> Matrix<T, N, N>
where
    T: Default + Copy + Sub<Output = T> + Mul<Output = T> + AddAssign + Neg<Output = T>,
    [(); N - 1]:,
{
    fn determinant(&self) -> T {
        match N {
            1 => self[0][0],
            2 => self[0][0] * self[1][1] - self[1][0] * self[0][1],
            _ => {
                let mut det = T::default();
                for j in 0..N {
                    let minor = self.minor_matrix(0, j);
                    if j % 2 == 0 {
                        det += self[0][j] * minor.determinant();
                    } else {
                        det += -(self[0][j] * minor.determinant());
                    };
                }
                det
            }
        }
    }
}

However, I should have already constrained N != 0.
The Rust compiler seems unable to infer generic recursion in minor.determinent() for Matrix<T,{R-1},{C-1}>
I'm exploring Rust's generics, so I'm not using LU.

While in cpp, I can easily do like:

template <class T, size_t R, size_t C>
auto Matrix<T, R, C>::det() const
  requires(R == C && std::is_arithmetic_v<T>)
{
  if constexpr (R == 1) {
    return data[0][0];
  } else if constexpr (R == 2) {
    return data[0][0] * data[1][1] - data[0][1] * data[1][0];
  } else {
    T determinant = 0;
    for (size_t c = 0; c < C; ++c) {
      T cofactor = ((c % 2 == 0) ? 1 : -1) * data[0][c];
      auto minor = minor_matrix(0, c);
      determinant += cofactor * minor.det();
    }
    return determinant;
  }
}

Ah, I guess recursion won't work out very well. Rust tries to avoid post-monomorphization errors in its type/trait system (therefore, the minor_matrix method literally does not exist on a 0x0 matrix, and the trait bounds enforce that fact even before monomorphization), and I should've known that the type system doesn't care whether something is reachable.

if false {
    let x = 1_i32.non_existent_method();
}

will throw an error, and for the same reason, it doesn't matter that the problematic function call is unreachable in the N==0 case. I don't think Rust has an equivalent of constexpr if statements... though there might be something similar with specialization? I briefly tried out implementing recursion and a base case with the min_specialization feature, but it indicated that the bound [(); N - 1]:, cannot be specialized on for whatever reason.

(There is typenum if you haven't heard of it, but feel free to have fun exploring Rust's generics and type system.)