The problem:
Chained calls to certain operators such as binary `*` and `+` may cause
unnecessary memory allocations. For example:
struct Vector {
coordinates: Vec<int>
}
impl Mul<int, Vector> for Vector {
fn mul(&self, rhs: &int) -> Vector {
let mut new_coordinates = self.coordinates.clone();
for c in new_coordinates.mut_iter() {
*c *= *rhs;
}
Vector { coordinates: new_coordinates }
}
}
fn get_vector() -> Vector {
let v = Vector { coordinates: vec!(1, 2, 3) };
v * 2 * 5
}
The last line of `get_vector` causes two new memory allocations. Preferably
that line wouldn't allocate at all; it should take the guts out of `v` and
multiply the coordinates in place.
The goal:
We want to be able to write the following function `calculate` and have it be
guaranteed that `calculate` doesn't cause unnecessary memory allocations.
fn calculate<X, T: Mul<X, T> + Add<T, T>>(value: T, mult: X) -> T {
value * mult * mult + value
}
Insufficient first idea for a solution:
Change the definition of `Mul` trait to:
pub trait Mul<RHS, Result> {
fn mul(self, rhs: &RHS) -> Result;
}
And then, change the implementation of `Mul` for `Vector` to:
impl Mul<int, Vector> for Vector {
fn mul(self, rhs: &int) -> Vector {
for c in self.coordinates.mut_iter() {
*c *= *rhs;
}
self
}
}
First of all, as a result of these changes, the `calculate` function wouldn't
compile complaining about the last use of `value` that: "error: use of moved
value: `value`". This could be fixed by changing the definition of `calculate`,
but this is not the main problem.
But the real problem is that for some types, the binary `*` operator shouldn't
move the `self` into the `mul` method. For example, when the return type of the
`mul` method is different from the type of `self` (and both are heap
allocated), then `mul` method is forced to allocate a new value which it
returns, and it should take `self` by reference.
My proposed solution:
Add a new keyword `stable` to the language. Marking a function argument
`stable` gives the guarantee to the caller of that function, that a variable
passed in as that argument is logically unchanged after the function call ends.
Then, change the definition of `Mul` trait to:
pub trait Mul<RHS, Result> {
fn mul(stable self, rhs: &RHS) -> Result;
}
Note: any other syntax for marking `self` as `stable` would be illegal.
One could implement `Mul` for any type by taking `self` by shared reference:
impl<T, RHS, Result> Mul<RHS, Result> for T {
fn mul(&self, rhs: &RHS) -> Result { ... }
}
Or, one could implement `Mul` for any type that implements `Copy` by taking
`self` by value:
impl<T: Copy, RHS, Result> Mul<RHS, Result> for T {
fn mul(self, rhs: &RHS) -> Result { ... }
}
Or, one could implement `Mul` for any type that implements `Clone` by taking
`self` by "stable value":
impl<T: Clone, RHS, Result> Mul<RHS, Result> for T {
fn mul(stable self, rhs: &RHS) -> Result { ... }
}
Taking an argument by "stable value" (as `self` above) means that any
(clonable) variable passed in as that argument is implicitly cloned before it's
passed in if the variable is potentially used after been passed in. For example:
impl Mul<int, Vector> for Vector {
fn mul(stable self, rhs: &int) -> Vector {
for c in self.coordinates.mut_iter() {
*c *= *rhs;
}
self
}
}
fn testing() {
let mut v = Vector { coordinates: vec!(1, 2, 3) };
v * 1; // Cloned due to not last use
v * 1; // Not cloned due to last use before assignment
v = Vector { coordinates: vec!(2, 4, 6) };
v * 1; // Cloned due to not last use
v = v * 1; // Not cloned due to last use before assignment
v * 1; // Not cloned due to last use
}
Open questions:
What should happen for example with `Rc` types w.r.t. `stable`:
impl<T, RHS, Result> Mul<RHS, Result> for Rc<T> {
fn mul(stable self, rhs: &RHS) -> Result { ... }
}
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev