1

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?

Stargateur
  • 24,473
  • 8
  • 65
  • 91
wyer33
  • 6,060
  • 4
  • 23
  • 53
  • 1
    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 11 '19 at 14:00

2 Answers2

6

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:

Stargateur
  • 24,473
  • 8
  • 65
  • 91
1

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)

SCappella
  • 9,534
  • 1
  • 26
  • 35