Help me understand traits better for my matrix struct

Hello everyone! I am relatively pretty new to Rust! I'm find this language beautiful to read. I took on a ray tracing challenge a couple weeks ago, and I'm now facing skill issue with traits.

#[derive(Debug, Clone, PartialEq)]
struct Matrix<T> {
    row: usize,
    col: usize,
    data: Vec<T>,
}

impl<T> Matrix<T> {
    fn new(row: usize, col: usize, data: Vec<T>) -> Self {
        Self { row, col, data } 
    }

    fn at(&self, row: usize, col: usize) -> Option<&T> {
        if row > self.row || col > self.col {
            return None;
        }

        self.data.get((row * self.col) * col)
    }
}

impl<T> Mul for Matrix<T>
where
    T: std::ops::Mul + Copy,
{
    type Output = Self;

    fn mul(self, rhs: Self) -> Self::Output {
        let mut res = Matrix::new(self.row, self.col, vec![]);

        for r in 0..self.row {
            for c in 0..self.col {
                let mut intersection_sum = 0;

                for i in 0..self.row {
                    let intersection_one = self.at(r, i).unwrap().clone(); // type is T 
                    let intersection_two = self.at(i, c).unwrap().clone(); // type is T
                    let mul = intersection_one * intersection_two: // type is <T as Mul<Self>>Output

                    intersection_sum += mul; // cannot add-assign `<T as std::ops::Mul>::Output` to `{integer}
                    // the trait `std::ops::AddAssign<<T as std::ops::Mul>::Output> is not implemented for {integer}
                }
            }
        }
    }
}

What I don't understand is that the type of intersection_one is T, and intersection_two is T. But when multiplying, it becomes <T as Mul<Self>>Output. I'm reading this as Mul's Output is T?

For intersection_sum += mul, I understand that there's not trait that defines how += behaves, so do I simply create a new impl AddAssign for Matrix? But Matrix isn't the intended type, it should be T?

I would really like feedback here. I'm trying to go back to the basics but I'm not sure how to advance from here. If anyone could give tips or resources to traits, that would be really appreciated!

the lang item Mul has an generic type parameter Rhs and an associated typeOutput, the Rhs is what you can put on the right of the * operator, and the associated Output type is the type of the result.

the <T as Mul<Self>>Output> is the type for the expression t1 * t2 where both t1 and t2 are of type T. for the primitive number types and probably most user defined types, it is probably the same as T, but it doesn't have to be. for example, suppose you have a custom Vector type, you decided to use the * operator for the inner product (I would not really use the * operator for this, but this is just an example), then the output should be a scalar type, something like:

impl Mul<Vector> for Vector {
    type Output = f64;
    //...
}

you can add an equality contraint for the associated type like this:

//...
where T: Mul<T, Output=T> + Copy,
//...

this will restrict the type of the expression t1 * t2 to be the same as t1 and t2 as well, all of which are T.

actually, there is, it is a lang item called AddAssign, it is implemented for all primitive types in the standard library, but you can implement it for user defined types too.

2 Likes