16
votes

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.

1

1 Answers

11
votes

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:

fn get_qux(&mut self) -> &mut Qux {
    &mut self.qux
}

This function indicates that all of self will be borrowed mutably, and that the returned reference will live as long as self 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 with self.bars, so there is no error.

You can make your original example work by adding a new scope:

fn run(&mut self) {
    {
        let mut qux = self.get_qux();
        let qux_mut = &mut qux;
        qux_mut.baz = true;
    }

    for bar in &self.bars {
        println!("{:?}", bar);
    }
}

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:

let mut qux = &mut self.qux;

for bar in &self.bars {
    qux.baz = ! qux.baz;
    println!("{:?}", bar);
}

Or the simpler:

for bar in &self.bars {
    self.qux.baz = ! self.qux.baz;
    println!("{:?}", bar);
}

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.