Adding two vector elements without copy or cloning them

I have a case where the elements of a vector might be very large and I do not want to copy or clone them just to perform numerical operations.

Below is an example that demonstrates what I am trying to do:

[derive(Debug,Clone)]
struct ValVector {
    vec : Vec<f32> ,
}
impl ValVector {
    pub fn new( v : Vec<f32> ) -> Self {
        Self{ vec : v }
    }
}
impl std::ops::Add for ValVector {
    type Output = Self;

    fn add(self : Self, rhs : Self)  -> Self {
        let mut result = self.clone();
        for i in 0 .. self.vec.len() {
            result.vec[i] += rhs.vec[i];
        }
        result
    }
}

fn main() {
    //
    let va = ValVector::new( vec![ 1f32, 2f32 ] );
    let vb = ValVector::new( vec![ 3f32, 5f32 ] );
    let vv = vec![va, vb];
    let vc = vv[0] + vv[1];
    //
    println!("vc = {:?}", vc);
}


Here is the compiler error I get:

... snip ...
   Compiling package v0.1.0 (/home/bradbell/trash/rust/package)
error[E0507]: cannot move out of index of `Vec<ValVector>`
  --> src/main.rs:28:14
   |
28 |     let vc = vv[0] + vv[1];
   |              ^^^^^ move occurs because value has type `ValVector`, which does not implement the `Copy` trait
   |
help: consider cloning the value if the performance cost is acceptable
   |
28 |     let vc = vv[0].clone() + vv[1];
   | 
..., snip ...

you can use a reference type for Rhs, but the only way for the + operator to not move the lhs (i.e. Self) is to implement Add for &ValVector instead of ValVector:

impl<'a, 'b> Add<&'b ValVector> for &'a ValVector {
    type Output = ValVector;
    fn add(self, rhs: &'b ValVector) -> ValVector {
        ValVector::new(self.vec.iter().zip(rhs.vec.iter()).map(|(x, y)| x+y).collect())
    }
}

note this has the downside that you have to write something like this:

let va = ValVector::new(vec![1.0, 2.0]);
let va = ValVector::new(vec![3.0, 4.0]);
// notice the `&` operator
let vc = &va + &vb;

this is a little bit verbose, but it's just rust prefers explicit syntax.

2 Likes

out of scope rhs will not live. ?? interesting

can you use vec.extend_from_slice(&rhs)

1 Like

I am not sure how you are suggesting I use extend_from_slice ?

1 Like

I don't see, either. The code produced by the source above is relatively well optimized as it is. vec.extend_from_slice(&rhs) would only lead to something more complicated.

Another, lighter option is to use += instead of + (compiled result). It's quite common with types that are "heavier". Obviously, it only works if you don't intend to use the (original) first operand later.

use std::ops::AddAssign;

#[derive(Debug, Clone)]
pub struct ValVector {
    vec: Vec<f32>,
}
impl ValVector {
    pub fn new(v: Vec<f32>) -> Self {
        Self { vec: v }
    }
}

impl AddAssign<&ValVector> for ValVector {
    fn add_assign(&mut self, rhs: &ValVector) {
        self.vec.iter_mut()
            .zip(&rhs.vec)
            .for_each(|(a, b)| *a += b);
    }
}

pub fn main() {
    let mut va = ValVector::new( vec![ 1f32, 2f32 ] );
    let vb = ValVector::new( vec![ 3f32, 5f32 ] );
    va += &vb;
    println!("va = {va:?}");
}
2 Likes

It's std so I think it's better.
link
P/s: one is adding vec and one is extending vec. It's different result. So...
P/s: I think it is a extending at first :slight_smile:

The following works for me but I have a few questions (see below the code):


#[derive(Debug,Clone)]
struct ValVector {
    vec : Vec<f32> ,
}
impl ValVector {
    pub fn new( v : Vec<f32> ) -> Self {
        Self{ vec : v }
    }
}
impl<'a> std::ops::Add<&'a ValVector> for &'a ValVector { 
    type Output = ValVector;

    fn add(self : Self, rhs : Self)  -> ValVector {
        let mut result = self.clone();
        for i in 0 .. self.vec.len() {
            result.vec[i] += rhs.vec[i];
        }
        result
    }
}

fn main() {
    //
    let va     = ValVector::new( vec![ 1f32, 2f32 ] );
    let vb     = ValVector::new( vec![ 3f32, 5f32 ] );
    let vc     = ValVector::new( vec![ 0f32, 0f32 ] );
    let mut vv = vec![va, vb, vc];
    vv[2]      = &vv[0] + &vv[1];
    //
    println!("vv = {:?}", vv);
}
  1. Why do I need the lifetime parameter 'a . It does not appear in the return value signature, and so I do not know what affect it has ?
  2. Would you expect the approach above to be slower that the iter / zip / map / collect approach above ?

zip truly hight more performance . link
but It have more size . But why ?
Update:link. zip even worse than simple for ?

This doesn't work due to using Self...

impl std::ops::Add<&ValVector> for &ValVector { 
    //             ^^^^^^^^^^---+  The type underlined here
    type Output = ValVector; // |  has to match the overlined type below
    //                        vvvv
    fn add(self : Self, rhs : Self)  -> ValVector {

...which is more obvious if you write out all the lifetimes.

impl<'a, 'b> std::ops::Add<&'a ValVector> for &'b ValVector { 
    type Output = ValVector;
    // `Self` is `&'b ValVector`, not `&'a ValVector`
    fn add(self : Self, rhs : Self)  -> ValVector {
error[E0308]: method not compatible with trait
  --> src/main.rs:12:5
   |
12 |     fn add(self : Self, rhs : Self)  -> ValVector {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected signature `fn(&'b ValVector, &'a ValVector) -> ValVector`
              found signature `fn(&'b ValVector, &'b ValVector) -> ValVector`

You can write it like this instead:

impl std::ops::Add<&ValVector> for &ValVector { 
    type Output = ValVector;
    fn add(self : Self, rhs : &ValVector)  -> ValVector {

(This implementation technically does more than the trait requires as the lifetime in rhs doesn't need to be related to the lifetimes in the header now, but that is okay -- similar to how you can leave off some where Self: Clone bound or the like on a trait method implementation, if you don't actually need the bound in the implementation yourself.)

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.