Reputation: 6340
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?
Upvotes: 1
Views: 290
Reputation: 26697
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:
Upvotes: 6
Reputation: 10424
If you only require Real
to implement Add
then you'll have to consume both Real
s 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 Real
s 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);
}
Upvotes: 1