0
votes

Because rust doesn't track individual field borrows across method calls - it assumes you've borrowed from the whole struct, it's not possible to borrow a field from the struct, return it, and then pass that into other mutable methods. That sounds contrived but I have this exact issue right now.

    struct Foo {
        x: i32,
        y: i32
    }
    
    impl Foo {
        pub fn borrow_x(&mut self) -> &mut i32 {
            &mut self.x
        }
    
        pub fn cross_product(&mut self, other: &mut i32) {
            self.y *= *other;
            *other *= self.y;
        }
    }
    
    fn main() {
        let mut foo = Foo{x: 2, y: 4};
        let x_ref = foo.borrow_x();
        foo.cross_product(x_ref);
    
        println!("x={} y={}", foo.x, foo.y);
    }

This of course isn't permitted in rust, the error from the compiler is:

error[E0499]: cannot borrow `foo` as mutable more than once at a time
  --> src/main.rs:20:5
   |
19 |     let x_ref = foo.borrow_x();
   |                 --- first mutable borrow occurs here
20 |     foo.cross_product(x_ref);
   |     ^^^               ----- first borrow later used here
   |     |
   |     second mutable borrow occurs here

My first reaction is to use unsafe to tell the compiler that it doesn't really borrow from self:

    pub fn borrow_x<'a>(&mut self) -> &'a mut i32 {
        unsafe {
            std::mem::transmute::<&mut i32, &'a mut i32>(&mut self.x)
        }
    }

It works, but it requires that you don't actually break rust's aliasing rules and reference foo.x elsewhere by accident.

Then I thought maybe I could split the struct so that the borrow and cross_product methods are on different sibling structs. This does work:

struct Bar {
    x: X,
    y: Y,
}

struct X {
    inner: i32
}

impl X {
    pub fn borrow_x(&mut self) -> &mut i32 {
        &mut self.inner
    }
}

struct Y {
    inner: i32
}

impl Y {
    pub fn cross_product(&mut self, other: &mut i32) {
        self.inner *= *other;
        *other *= self.inner;
    }
}

fn main() {
    let mut bar = Bar{x: X{inner: 2}, y: Y{inner: 4}};
    let x_ref = bar.x.borrow_x();
    bar.y.cross_product(x_ref);

    println!("x={} y={}", bar.x.inner, bar.y.inner);
}

I think one could ditch the method all-together and pass references to both fields into cross_product as a free function. That should be equivalent to the last solution. I find that kind of ugly though, especially as the number of fields needed from the struct grow.

Are there other solutions to this problem?

1

1 Answers

2
votes

The thing to keep in mind is that rust is working at a per-function level.

        pub fn cross_product(&mut self, other: &mut i32) {
            self.y *= *other;
            *other *= self.y;
        }

The mutability rules ensure that self.y and other are not the same thing. If you're not careful you can run into this kind of bug in C++/C/Java quite easily.

i.e. imagine if instead of

        let x_ref = foo.borrow_x();
        foo.cross_product(x_ref);

you were doing

        let y_ref = foo.borrow_y();
        foo.cross_product(y_ref);

What value should foo.y end up with? (Allowing these to refer to the same object can cause certain performance optimisations to not be applicable too)

As you mentioned, you could go with a free function

fn cross_product(a: &mut i32, b: &mut i32) {
  a *= b;
  b *= a;
}

but I'd consider ditching mutability altogether

fn cross_product(a:i32, b:i32) -> (i32,i32) {
  (a*b, a*b*b)
}

ASIDE: If the return value seems odd to you (it did to me initially too) and you expected (a*b, a*b) you need to think a little harder... and I'd say that alone is a good reason to avoid the mutable version.

Then I can use it like this

let (_x,_y) = cross_product(foo.x, foo.y);
foo.x = _x; 
foo.y = _y;

This is a bit verbose, but when RFC-2909 lands we can instead write:

(foo.y, foo.x) = cross_product(foo.x, foo.y);