Implement a trait with a method containing a const generic parameter when the value of the generic does not matter

The code below tries to define multiplication in a general way so it would work for types with dimensions (like matrices) but also for normal integers. The main issue that I am facing is that I need to specify a dimension for dimensionless things like integer. See the comments at the bottom statements.

I would like to implement MulLeftDimension for MulLeft and drop the generic argument of the mul_left method as there is no meaning for the parameter for this implementation, is this somehow possible?

trait MulLeft {
    type One;
    type Multiplier;
    type MultiplicationResult;

    fn one() -> Self::One;

    fn mul_left_no_dim(self, a: Self::Multiplier) -> Self::MultiplicationResult;
}

impl MulLeft for usize {
    type One = usize;
    type Multiplier = usize;
    type MultiplicationResult = usize;
    
    fn one() -> usize { 1 }
    
    fn mul_left_no_dim(self,  a: Self::Multiplier) -> Self::MultiplicationResult {
        a * self
    }
}

trait MulLeftDimension {
    type One;
    type Multiplier<const RA: usize>;
    type MultiplicationResult<const RA: usize>;

    fn one() -> Self::One;

    fn mul_left<const RA: usize>(self, a: Self::Multiplier<RA>) -> Self::MultiplicationResult<RA>;
}

impl<T: MulLeft> MulLeftDimension for T {
    type Multiplier<const RA: usize> = T::Multiplier;
    type MultiplicationResult<const RA: usize> = T::MultiplicationResult;
    type One = T::One;

    fn one() -> Self::One {
        T::one()
    }

    // I would like to remove this const generic in this implementation as its value is not important
    fn mul_left<const RA: usize>(self, a: T::Multiplier) -> T::MultiplicationResult {
        self.mul_left_no_dim(a)
    }
}

impl<const R: usize, const C: usize> MulLeftDimension
    for Matrix<R, C>
{
    type One = Matrix<R, R>;
    type Multiplier<const RA: usize> = Matrix<RA, R>;
    type MultiplicationResult<const RA: usize> = Matrix<RA, C>;

    fn one() -> Self::One {
        Matrix::<R, R>::one()
    }

    fn mul_left<const RA: usize>(self, lhs: Self::Multiplier<RA>) -> Self::MultiplicationResult<RA> {
        let mut res = Self::MultiplicationResult::<RA>::default();
        for r in 0..RA {
            for c in 0..C {
                for i in 0..R {
                    res.rows[r][c] += lhs.rows[r][i] * self.rows[i][c];
                }
            }
        }
        res
    }
}

#[derive(Debug)]
struct Matrix<const R: usize, const C: usize> {
    rows: [[usize; C]; R],
}

impl<const A: usize> Matrix<A, A> {
    fn one() -> Self {
        let mut s = Self::default();
        for r in 0..A {
            s.rows[r][r] = 1;
        }

        s
    }
}

impl<const A: usize, const B: usize> std::default::Default for Matrix<A, B> {
    fn default() -> Self {
        Self { rows: [[0; B]; A] }
    }
}

impl<const A: usize, const B: usize, const C: usize> std::ops::Mul<Matrix<B, C>> for Matrix<A, B> {
    type Output = Matrix<A, C>;

    fn mul(self, rhs: Matrix<B, C>) -> Self::Output {
        rhs.mul_left(self)
    }
}

fn main() {
    let m1: Matrix<1, 2> = Matrix {
        rows: [
            [5, 7],
        ]
    };
    
    let m2: Matrix<2, 3> = Matrix {
        rows: [
            [1,1,1],
            [1,1,1],
        ]
    };
    let m3 = m1 * m2;
    println!("{:?}", m3);
    
    let m3 = m3.mul_left(Matrix::<1, 3>::one());
    println!("{:?}", m3);
    
    let number = 5.mul_left::<1>(10); // need to specify an unused value for RA
    println!("{:?}", number);
    
    // This fails: 'cannot infer the value of the const parameter `RA` declared on the method `mul_left`'
    let number = 5.mul_left(10); // need to specify an unused value for RA
    println!("{:?}", number);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed
   --> src/main.rs:125:20
    |
125 |     let number = 5.mul_left(10); // need to specify an unused value for RA
    |                    ^^^^^^^^ cannot infer the value of the const parameter `RA` declared on the method `mul_left`
    |
help: consider specifying the generic argument
    |
125 |     let number = 5.mul_left::<RA>(10); // need to specify an unused value for RA
    |                            ++++++

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground` (bin "playground") due to previous error

There are two fundamental problems with your code:

  1. You are putting the generic bounds on everything in the trait, instead of putting it on the trait itself. If you fix this, you will have a trait MulLeftDimension<RA>, and then you can implement MulLeftDimension<0> for scalars.
  2. However, the ambiguity then arises at another place, in Matrix::one(). You are not respecting single responsibility, because one() doesn't depend on RA, yet it can only be called if RA is known, given that it's the member of the trait MulLeftDimension<RA>. To fix this, move the getter of the unity value to another, non-parametric trait.

All in all, this compiles without any type annotations.

1 Like

Thanks. The main issue was me not respecting single responsibility and then trying many different options on getting it to work. I used to have RA on the trait level, but that yielded the problem for one() you described.

EDIT: To make it more general (and allow multiplication of usize with Matrix) I could just use generics instead of associated types: Rust Playground

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.