[rust-dev] &self/&mut self in traits considered harmful(?)

Tommi rusty.gates at icloud.com
Fri Jun 13 03:14:42 PDT 2014


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 { ... }
}

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20140613/0a048e3d/attachment.html>


More information about the Rust-dev mailing list