Here's a generic function that makes a vector of sums. It works when I write T: Copy, U: Copy, but does not this way, because it can't move val1, val2 from behind a shared reference.
So, question 1: do I understand this right: when you do even a simple math calculation, you must either borrow & copy, or move the value? (question 2: And for numeric types copy happens silently under the hood?)
use std::ops::Add;
fn add<T, U>(v1: Vec<T>, v2: Vec<U>) -> Vec<T>
where T: From<U> + Add<T> + Add<Output = T>
{
let sum_func = |x: (&T, &U)| -> T {
let (val1, val2) = x;
(*val1.clone()) + T::from(*val2.clone())
};
v1.iter().zip(v2.iter()).map(sum_func).collect::<Vec<T>>()
}
fn main() {
let vec1:Vec<i32> = vec![1, 2, 5, 5, 12, 20];
let vec2:Vec<f64> = vec![1.5, 2.5, 5.5, 5.5, 12.5, 20.5];
println!("{:?}", add(vec2, vec1));
}
question 3: I see Copy trait implements Clone. For numeric calculations, is it ok performance-wise to just clone val1/val2, or it's better to require Copy?
It depends on the Add implementations available. It's technically possible (but discouraged in case of non-trivial values) to have an implementation which goes from &T + &U to V directly.
If the value is Copy, it's guaranteed that it can be memcpyd without problems. In that case, Clone is expected (not obliged, but expected) to behave exactly like Copy.
If the value isn't Copy, then cloning it can have arbitrary effects like memory allocation.
So, it's up to you whether you can tolerate these effects in your code or not. But for Copy types, there should be no difference (and the exceptions are probably some degenerate cases).
Answer 1:
Yes, you can't move an object out of a shared reference, because that would mean taking ownership of it and rust only has a single owner for all data.
Answer 2:
For all types implementing copy it will be used when moved out of a shared reference or when passing as a value to a function.
Answer 3:
I think Clone::clone() is generally slower than copying you data. But I'm not 100% sure.
Bonus tipp:
You already own the vectors in the add function. Then you can simply use
Wouldn't it be better to have an impl for &T + &U -> V that allocates, than to require ownership and thus require cloning an input whose storage cannot necessarily be reused? Or am I misunderstanding the alternative proposed?
The best way in many cases probably is to do as it is done with String, i.e. to have T + &U -> V - in this case, you can reuse the allocation from the left side, but not force the user to give up ownership of the right side.
If you're going to do that, it's probably best to implement AddAssign and then dispatch to it in a generic Add implementation, so that they always stay in sync:
(untested)
impl<U> Add<U> for MyType where Self: AddAssign<U> {
type Output=Self;
fn add(mut self, rhs:U)->Self { self += rhs; self }
}
You can only reuse the allocation from the left side in some cases (if there is enough spare space). So a.clone() + &b may do two allocations, whereas &a + &b would only do one allocation. So &a + &b is better.