0

So I've been trying to implement a library for vector and matrix maths, and I created some functions that worked alright but wanted to generalize for all number primitives and add the functionality into the normal operators.

My thought was that I'd create a container for a Vec<T>, that can contain either number types (like i32) or another container for Vec, so that matrices where possible. Ergo:

#[derive(Clone, Debug)]
struct Mat<T>(Vec<T>);

Then, to add together two vecs of any number I implement Add as:

impl<'a, T> Add for &'a Mat<T>
where T: PartialEq + PartialOrd + Add<T> + Sub<T> + Mul<T> + Div<T> + Rem<T> + Clone {
    type Output = Option<Mat<<T as std::ops::Add>::Output>>;

    fn add(self, other: &Mat<T>) -> Self::Output {
        let a: &Vec<T> = self.pop();
        let b: &Vec<T> = other.pop();
        match a.len() == b.len() {
            true => {
                let mut retvec: Vec<<T as std::ops::Add>::Output> = Vec::new();
                for i in 0..a.len() {
                    retvec.push(a[i].clone() + b[i].clone());
                }
                Some(Mat(retvec))
            },
            false => None
        }
    }
}

Edit: To further clarify, Mat::pop() is just the unwrap function, though probably poorly named.

The basic scenario of adding together two vectors of any number seems to work.

#[test]
fn add_override_vectors() {
    let vec: Mat<i32> = Mat(vec![2, 2, 2]);
    let newvec = &vec + &vec;

    assert_eq!(*newvec.unwrap().pop(), vec![4,4,4]);
}

But matrices are giving me a headache. For them, the add function looks very similar, except for the let Some(x) statement:

impl<'a, T> Add for &'a Mat<Mat<T>>
where T: Add<&'a Mat<T>>{
    type Output = Option<Mat<T>>;

    fn add(self, other: &Mat<Mat<T>>) -> Self::Output {
        let a: &Vec<Mat<T>> = self.pop();
        let b: &Vec<Mat<T>> = other.pop();
        match a.len() == b.len() {
            true => {
                let mut retvec: Vec<T> = Vec::new();
                for i in 0..a.len() {
                    if let Some(x) = &a[i] + &b[i] {
                        retvec.push(x);
                    }
                }
                Some(Mat(retvec))
            },
            false => None
        }
    }
}

The error message I get is:

error[E0369]: binary operation `+` cannot be applied to type `&Mat<T>`
  --> src\main.rs:46:38
   |
46 |                     if let Some(x) = &a[i] + &b[i] {
   |                                      ^^^^^^^^^^^^^
   |
   = note: an implementation of `std::ops::Add` might be missing for `&Mat<T>`

So the compiler says that Add might not be implemented for &Mat<T>, but I thought that I've specified the bound so that it has that requirement in where T: Add<&'a Mat<T>. To me it seems that whatever is in &a[i] should have the Add trait implemented. What am I doing wrong here?

Just as extra clarification, my idea is that Add for &'a Mat<Mat<T>> should be able to be called recursively until it boils down to the Vec with an actual number type in it. Then the Add for &'a Mat<T> should be called.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
hugo.r
  • 13
  • 4
  • Possible duplicate of [How to write a trait bound for adding two references of a generic type?](https://stackoverflow.com/questions/34630695/how-to-write-a-trait-bound-for-adding-two-references-of-a-generic-type) – trent Sep 04 '18 at 12:24
  • Hmm, I think I misread. Did you mean to add a `T` to a `Mat` somewhere inside the implementation? That's what `T: for<'b> Add<&'b Mat>` would imply. I suspect you actually want `for<'b> &'b Mat: Add` (but that causes more problems) – trent Sep 04 '18 at 12:34
  • I'm not sure I get what you're asking for, if you mean that i forgot to implement an Add function for a `T` and a `Mat` then I don't think i would need one, since the forloop loops through both vectors in both functions, it would only ever need to add either `T + T` or `Mat + Mat`, right? Otherwise, if you mean if I tried to do `T + Mat`, then no, the test I posted is the only test I have as of yet. – hugo.r Sep 04 '18 at 13:11
  • Your `where` clause is appropriate for `T + &Mat` but what you're actually doing inside the function is `&Mat + &Mat` (I think). But that's not the whole story because fixing the bound leads to other issues. – trent Sep 04 '18 at 14:37
  • I think that, instead of `&a[i] + &b[i]`, you want `a[i].clone() + b[i].clone()` – Phoenix Sep 04 '18 at 16:56

1 Answers1

1

There are two problems: the wrong associated Output type and the type of retvec

Something like that should work:

impl<'a, T> Add for &'a Mat<Mat<T>>
where
    T: PartialEq + PartialOrd + Add<T> + Clone,
{
    type Output = Option<Mat<Mat<<T as std::ops::Add>::Output>>>;

    fn add(self, other: &Mat<Mat<T>>) -> Self::Output {
        let a: &Vec<Mat<T>> = self.pop();
        let b: &Vec<Mat<T>> = other.pop();
        match a.len() == b.len() {
            true => {
                let mut retvec: Vec<Mat<<T as std::ops::Add>::Output>> = Vec::new();
                for i in 0..a.len() {
                    if let Some(x) = &a[i] + &b[i] {
                        retvec.push(x);
                    }
                }
                Some(Mat(retvec))
            }
            false => None,
        }
    }
}

A part the compilation issue I think it is not correct to implement a trait for a "recursive" struct like Mat<Mat<T>>, if you think X as type X = Mat<T> then the impl for Mat<T> suffices:

impl<'a, T> Add for &'a Mat<T>
where
    T: PartialEq + PartialOrd + Add<T> + Clone

with the additional impl for Mat<T> values:

impl<T> Add for Mat<T>
where
    T: PartialEq + PartialOrd + Add<T> + Clone

Below I post a full working code, please note that the Output type is no more an Option<Mat<T>> but a plain Mat<T> object: this avoids a lot of headaches and probably it is conceptually wrong if you want to impl some type of algebra.

use std::ops::*;
use std::vec::Vec;

#[derive(Clone, Debug, PartialEq, PartialOrd)]
struct Mat<T>(Vec<T>);

impl<T> Mat<T> {
    fn pop(&self) -> &Vec<T> {
        &self.0
    }
}

impl<T> Add for Mat<T>
where
    T: PartialEq + PartialOrd + Add<T> + Clone,
{
    type Output = Mat<<T as std::ops::Add>::Output>;

    fn add(self, other: Mat<T>) -> Self::Output {
        let a: &Vec<T> = self.pop();
        let b: &Vec<T> = other.pop();
        match a.len() == b.len() {
            true => {
                let mut retvec: Vec<<T as std::ops::Add>::Output> = Vec::new();
                for i in 0..a.len() {
                    retvec.push(a[i].clone() + b[i].clone());
                }
                Mat(retvec)
            }
            false => Mat(Vec::new()),
        }
    }
}

impl<'a, T> Add for &'a Mat<T>
where
    T: PartialEq + PartialOrd + Add<T> + Clone,
{
    type Output = Mat<<T as std::ops::Add>::Output>;

    fn add(self, other: &Mat<T>) -> Self::Output {
        let a: &Vec<T> = self.pop();
        let b: &Vec<T> = other.pop();
        match a.len() == b.len() {
            true => {
                let mut retvec: Vec<<T as std::ops::Add>::Output> = Vec::new();
                for i in 0..a.len() {
                    retvec.push(a[i].clone() + b[i].clone());
                }
                Mat(retvec)
            }
            false => Mat(Vec::new()),
        }
    }
}


#[test]
fn add_override_vectors() {
    let vec: Mat<Mat<i32>> = Mat(vec![Mat(vec![2, 2, 2]), Mat(vec![3, 3, 3])]);
    let newvec = &vec + &vec;

    assert_eq!(*newvec.pop(), vec![Mat(vec![4, 4, 4]), Mat(vec![6, 6, 6])]);
}

#[test]
fn add_wrong_vectors() {
    let vec1: Mat<Mat<i32>> = Mat(vec![Mat(vec![2, 2, 2]), Mat(vec![4, 4, 4])]);
    let vec2: Mat<Mat<i32>> = Mat(vec![Mat(vec![3, 3, 3]), Mat(vec![3, 3])]);
    let newvec = &vec1 + &vec2;

    assert_eq!(*newvec.pop(), vec![Mat(vec![5, 5, 5]), Mat(vec![])]);
}


fn main() {
    let vec: Mat<Mat<i32>> = Mat(vec![Mat(vec![1, 2, 2]), Mat(vec![3, 3, 3])]);
    let newvec = &vec + &vec;

    println!("Hello, world!: {:?}", newvec);
}

PS: Your Mat<T> type is not a matrix in the classical sense, perhaps another name should be more appropriate to avoid confusion.

attdona
  • 17,196
  • 7
  • 49
  • 60
  • Edit: Nope, stupid me. Forgot to derive PartialEq and PartialOrd. As I said, thanks a lot! How's this not matrices btw? I've had them described to me as vectors of vectors. Thanks man, good explanation! Yeah, I agree that the `Mat>` conceptually weak, but that was me just fighting the compiler. Even now that I've copied over your code it wont compile if I leave the `Mat>` tests in. Still the same error: "An implementation of `std::ops::Add` might be missing for `Mat>`", and you saying that it worked for you is making me doubt the existence logic and reason. – hugo.r Sep 06 '18 at 08:28
  • Usually a matrix is defined as multidimensional array with the same cardinality in each dimension. With your `Mat>` type you can have rows of different size. – attdona Sep 06 '18 at 08:57
  • Right, but it won't actually be able to count those malformed matrices. What would you suggest to fix that, a macro or maybe a `Mat::new()` member function that makes it conform to the right vector dimensions? – hugo.r Sep 06 '18 at 13:18