First, let the code speak:
#[derive(Debug)]
struct Bar;
#[derive(Debug)]
struct Qux {
baz: bool
}
#[derive(Debug)]
struct Foo {
bars: Vec<Bar>,
qux: Qux,
}
impl Foo {
fn get_qux(&mut self) -> &mut Qux {
&mut self.qux
}
fn run(&mut self) {
// 1. Fails:
let mut qux = self.get_qux();
// 2. Works:
// let mut qux = &mut Qux { baz: false };
// 3. Works:
// let mut qux = &mut self.qux;
let qux_mut = &mut qux;
qux_mut.baz = true;
for bar in &self.bars {
println!("{:?}", bar);
}
}
}
fn main() {
println!("Hello, world!");
let mut foo = Foo { bars: vec!(), qux: Qux { baz: false } };
foo.run();
}
This errors:
error[E0502]: cannot borrow `self.bars` as immutable because `*self` is also borrowed as mutable
--> src/main.rs:33:21
|
22 | let mut qux = self.get_qux();
| ---- mutable borrow occurs here
...
33 | for bar in &self.bars {
| ^^^^^^^^^ immutable borrow occurs here
...
36 | }
| - mutable borrow ends here
If I uncomment either 2.
or 3.
, why does it compile just fine? The called function in 1.
doesn't do anything drastically different from 2.
or 3.
. So why is it then that 1.
fails to compile?
Although there are many similar titled questions, I could not clearly identify this as a dupe (other than the error message being the same), possibly because of my lack of understanding of the ownership/borrowing system in Rust.
In Rust, the compiler stops at the function call boundary when evaluating generic parameters, which includes generic lifetime parameters. In your case 1, you are calling a method:
This function indicates that all of
self
will be borrowed mutably, and that the returned reference will live as long asself
will. During this time, no other borrows (mutable or not) of self or it's components may be made.In your second case, you make up a completely new
Qux
that has no attachment to your struct whatsoever. It's not a really great example, because it has very different meaning. If this case works for you, you should do that. However, you won't be modifying the same thing as case 1.In the third case, you avoid the function call. That means that the compiler has a bit more information about what exactly is borrowed. Specifically, it can see that
self.qux
doesn't interact at all withself.bars
, so there is no error.You can make your original example work by adding a new scope:
Here, the artificial scope clearly defines where the mutable borrow ends. Once the borrow is over, other items are allowed to make new borrows.
If you need to modify
qux
inside the loop, then you are required to follow the third pattern:Or the simpler:
Many times, you can refactor your code to create new structs that have information and encapsulate a nice mutation boundary to make code like this.