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?