Binary operation `+` cannot be applied to type :DataRow<T>

Can anyone spot what I need to change to get this to compile. I'm still learning rust and this is just some code for an exercise I'm doing where I'm trying to parse some revenue and expense tables and add them. I want to store the data in these DataTable structs and I'm trying to implement the Add trait. I've successfully done so for DataRow. Just can't figure out what I need to include to get past the compiler error.

The error occurs when trying to map over the rows and add them with the + operator. Its strange because i HAVE successfully implemented the Add trait for DataRow. There is a working test included. Why doesn't the compiler recognize the Add impl?

#[derive(Debug, Eq, PartialEq)]
pub struct Header {
  pub names: Vec<String>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct DataRow<T> {
  pub name: String,
  pub data: Vec<T>,
}

impl<T> DataRow<T> {
  pub fn new(name: String, data: Vec<T>) -> Self {
    DataRow { name, data }
  }
}

use std::ops::Add;
impl<T: Add<Output = T> + Copy> Add for DataRow<T> {
  type Output = DataRow<T>;
  fn add(self, other: DataRow<T>) -> Self::Output {
    DataRow::new(
      self.name,
      self
        .data
        .iter()
        .zip(other.data.iter())
        .map(|(&item, &otheritem)| item + otheritem)
        .collect(),
    )
  }
}

#[test]
fn testaddrows() {
  let r1 = DataRow::new("asdf".to_string(), vec![1, 2, 3]);
  let r2 = DataRow::new("asdf".to_string(), vec![1, 2, 3]);
  assert_eq!(r1 + r2, DataRow::new("asdf".to_string(), vec![2, 4, 6]));
}

pub struct DataTable<T> {
  pub header: Header,
  pub rows: Vec<DataRow<T>>,
}

impl<T> DataTable<T> {
  pub fn new(header: Header, rows: Vec<DataRow<T>>) -> Self {
    DataTable { header, rows }
  }
}

impl<T: Add<Output = T>> Add for DataTable<T> {
  type Output = DataTable<T>;
  fn add(self, other: DataTable<T>) -> Self::Output {
    DataTable::new(
      self.header,
      self
        .rows
        .iter()
        .zip(other.rows.iter())
        .map(|(&row, &otherrow)| row + otherrow)
        .collect(),
    )
  }
}

(Playground)

Your impl for DataRow has a Copy bound on T, but your impl on DataTable hasn't. Can't test rightnow, but that looks like a problem.

Thanks. The "+ Copy" is needed on the DataRow. If removed, I get the error:

error[E0507]: cannot move out of borrowed content                           
  --> src\table_types.rs:27:16                                              
   |                                                                        
27 |         .map(|(&item, &otheritem)| item + otheritem)                   
   |                ^----                                                   
   |                ||                                                      
   |                |hint: to prevent move, use `ref item` or `ref mut item`
   |                cannot move out of borrowed content                     
                                                                            

I tried adding the + Copy along with + Clone to both DataRow and DataTable but that didn't work.

Well, those are other errors that surface because you fixed the first one. The problem is your impl of Add that consumes its arguments (it's a problem for DataTable, too, but that doesn't surface because you're not using it), but you're trying to use it on shared references. Not knowing your use case makes me guess, but a compiling version can be found at Rust Playground

2 Likes

Thanks so much for your help. I was able to get something working by following your example and adding similar lifetime params to the DataTable. After adding some more derives for Clone and Eq in a few more places, I got a passing test adding 2 DataTables with +.

Thanks again for taking the time to help with this.

Here is the working code:

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct Header {
  pub names: Vec<String>,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct DataRow<T> {
  pub name: String,
  pub data: Vec<T>,
}

impl<T> DataRow<T> {
  pub fn new(name: String, data: Vec<T>) -> Self {
    DataRow { name, data }
  }
}

use std::ops::Add;
impl<'a, T: Add<Output = T> + Copy> Add for &'a DataRow<T> {
  type Output = DataRow<T>;
  fn add(self, other: &'a DataRow<T>) -> Self::Output {
    DataRow::new(
      self.name.clone(),
      self
        .data
        .iter()
        .zip(other.data.iter())
        .map(|(&item, &otheritem)| item + otheritem)
        .collect(),
    )
  }
}

#[test]
fn testaddrows() {
  let r1 = DataRow::new("asdf".to_string(), vec![1, 2, 3]);
  let r2 = DataRow::new("asdf".to_string(), vec![1, 2, 3]);
  assert_eq!(&r1 + &r2, DataRow::new("asdf".to_string(), vec![2, 4, 6]));
}

#[derive(Debug, Eq, PartialEq, Clone)]
pub struct DataTable<T> {
  pub header: Header,
  pub rows: Vec<DataRow<T>>,
}

impl<T> DataTable<T> {
  pub fn new(header: Header, rows: Vec<DataRow<T>>) -> Self {
    DataTable { header, rows }
  }
}

impl<'a, T: Add<Output = T> + Copy> Add for &'a DataTable<T> {
  type Output = DataTable<T>;
  fn add(self, other: &'a DataTable<T>) -> Self::Output {
    DataTable::new(
      self.header.clone(),
      self
        .rows
        .iter()
        .zip(other.rows.iter())
        .map(|(row, otherrow)| row + otherrow)
        .collect(),
    )
  }
}

#[test]
fn testaddtables() {
  fn makedummydt(factor: isize) -> DataTable<isize> {
    DataTable::new(
      Header {
        names: vec!["asdf".to_string(), "asdf".to_string()],
      },
      vec![
        DataRow::new(
          "asdf".to_string(),
          vec![1, 2, 3].iter().map(|i| i * factor).collect(),
        ),
        DataRow::new(
          "asdf".to_string(),
          vec![1, 2, 3].iter().map(|i| i * factor).collect(),
        ),
      ],
    )
  }
  let dt1 = makedummydt(1);
  let dt2 = makedummydt(1);

  assert_eq!(&dt1 + &dt2, makedummydt(2));
}

You don't have to iterate over references if you use into_iter instead of iter, consuming the Vec. This makes the implementation for non-references cleaner and erases the Copy bound:

impl<T: Add<Output = T>> Add for DataRow<T> {
  type Output = DataRow<T>;
  fn add(self, other: DataRow<T>) -> Self::Output {
    DataRow::new(
      self.name,
      self
        .data
        .into_iter()
        .zip(other.data)
        .map(|(item, otheritem)| item + otheritem)
        .collect(),
    )
  }
}

A similar transformation will work on Add for DataTable<T>.

In fact, you can have both implementations of Add (for references and non-references). What I would do for the reference version, though, is instead of requiring T: Add<Output=T> + Copy, require &'a T: Add<Output=T> so that you don't have to dereference to add the Ts. (Many types that implement Add also implement it for references.) Here's what I mean:

impl<'a, T> Add for &'a DataRow<T>
where
    &'a T: Add<Output = T>,
{
    type Output = DataRow<T>;
    fn add(self, other: &'a DataRow<T>) -> Self::Output {
        DataRow::new(
            self.name.clone(),
            self.data
                .iter()
                .zip(&other.data)
                .map(|(item, otheritem)| item + otheritem)
                .collect(),
        )
    }
}

T: Copy bounds should probably be rare. T: Clone is much more common, but in this case, all you need to do is stick with references and you don't need either.

1 Like

Thank you for explaining. I like this it is cleaner looking. This helps clear up what was happening I think. So because the first impl was using iter() which iterates over references, we had to add the lifetimes and Copy bound. But using into_iter() passes by value and thus takes ownership (consumes) the Vec. That way we don't have to worry about the Copy bound and the lifetimes... I hope I've at least understood what is happening at some level.

After using your DataRow impl and making similar changes to DataTable (below) I was able to add the structures together with no references.

impl<T: Add<Output = T>> Add for DataTable<T> {
  type Output = DataTable<T>;
  fn add(self, other: DataTable<T>) -> Self::Output {
    DataTable::new(
      self.header.clone(),
      self
        .rows
        .into_iter()
        .zip(other.rows.into_iter())
        .map(|(row, otherrow)| row + otherrow)
        .collect(),
    )
  }
}

Yep, this is an accurate summary. :sunglasses:

For reference, you can look at how the std library implements Add for numerical types. This might give you an understanding of what is needed for that level of convenience.

Looking at i32, The T + T impl is given here, while the ref-based impls are given here. There are 4 different impls of Add involved. (They are implemented using macros because it can be quite repetative to do this for all numerical types.)

1 Like