1
votes

What's the proper approach to compute arithmetic operations on a borrowed vector of elements that lack Copy in Rust? In the following code, I'd like foo to borrow a vector x and then compute a short function. The trick is that the elements in x necessarily lack the Copy trait. Anyway, the code

fn foo<Real>(x: &Vec<Real>) -> Real
where
    Real: std::ops::Add<Output = Real> + std::ops::Mul<Output = Real> + Clone,
{
    (x[0] + x[1]) * x[2]
}

fn main() {
    let x = vec![1.2, 2.3, 3.4];
    let _y = foo::<f64>(&x);
}

Fails to compile with the error

error[E0507]: cannot move out of index of `std::vec::Vec<Real>`
 --> src/main.rs:5:6
  |
5 |     (x[0] + x[1]) * x[2]
  |      ^^^^ move occurs because value has type `Real`, which does not implement the `Copy` trait

error[E0507]: cannot move out of index of `std::vec::Vec<Real>`
 --> src/main.rs:5:13
  |
5 |     (x[0] + x[1]) * x[2]
  |             ^^^^ move occurs because value has type `Real`, which does not implement the `Copy` trait

error[E0507]: cannot move out of index of `std::vec::Vec<Real>`
 --> src/main.rs:5:21
  |
5 |     (x[0] + x[1]) * x[2]
  |                     ^^^^ move occurs because value has type `Real`, which does not implement the `Copy` trait

This makes sense. The indexing attempts to move out borrowed content. That said, if we try to borrow on the indices:

fn foo<Real>(x: &Vec<Real>) -> Real
where
    Real: std::ops::Add<Output = Real> + std::ops::Mul<Output = Real> + Clone,
{
    (&x[0] + &x[1]) * &x[2]
}

fn main() {
    let x = vec![1.2, 2.3, 3.4];
    let _y = foo::<f64>(&x);
}

Then, we get a new compiler error:

error[E0369]: binary operation `+` cannot be applied to type `&Real`
 --> src/main.rs:5:12
  |
5 |     (&x[0] + &x[1]) * &x[2]
  |      ----- ^ ----- &Real
  |      |
  |      &Real
  |
  = note: an implementation of `std::ops::Add` might be missing for `&Real`

This also makes sense; the traits Add and Mul are on Real and not &Real. Nevertheless, I'm not sure how to resolve the error. Is there a straightforward fix?

2

2 Answers

6
votes

You just have to use arcane magic call "Higher-ranked trait bounds", once you learn this power you just have to use it:

fn foo<Real>(x: &[Real]) -> Real
where
    for<'a> &'a Real: std::ops::Add<Output = Real> + std::ops::Mul<Output = Real>,
{
    &(&x[0] + &x[1]) * &x[2]
}

fn main() {
    let x = vec![1.2, 2.3, 3.4];
    let _y = foo::<f64>(&x);
}

As you can't see we just have to ask the &Read implement Add and Mul but we need some sort of generic lifetime so we use for<'a> notation.

See:

1
votes

If you only require Real to implement Add then you'll have to consume both Reals to add them. You'll need to require Add for &Real if you want to add without consuming.

Alternatively, you've added the trait bound Clone for Real, which means you can clone the Reals before adding them.

use std::ops::{Add, Mul};

// You shouldn't ever use the type &Vec<T> as an input,
// since it's unnecessarily restrictive and introduces two layers of indirection
fn foo_clone<Real>(x: &[Real]) -> Real
where
    Real: Add<Output = Real> + Mul<Output = Real> + Clone,
{
    (x[0].clone() + x[1].clone()) * x[2].clone()
}

// This uses the higher-ranked trait bounds that Stargateur mentioned
// It basically means that the implementation of `Add` for `Real`
// can't restrict the lifetime.
fn foo_ref_add<Real>(x: &[Real]) -> Real
where
    for <'a> &'a Real: Add<Output = Real> + Mul<Output = Real>,
{
    &(&x[0] + &x[1]) * &x[2]
}

fn main() {
    let x = vec![1.2, 2.3, 3.4];
    let _y = foo_clone::<f64>(&x);
    let _z = foo_ref_add::<f64>(&x);
}

(playground)