Error[E0502]: cannot borrow as mutable because it is also borrowed as immutable


#1

Why I write this code correct?

struct Module {
    name: String,
    messages: Vec<String>,
}

impl Module {
    fn new(name: String) -> Module {
        Module {
            name: name,
            messages: Vec::new(),
        }
    }

    fn print(&mut self) {
        match self.messages.get(0) {
            Some(msg) => {}
            None => {
                self.messages.push("empty list".to_string());
            }
        }
        let messages = self.messages.join("\n");
        println!("Module {}\n{}", self.name, messages);
    }
}

fn main() {
    let mut module = Module::new("Main".to_string());
    module.print();
}
error[E0502]: cannot borrow `self.messages` as mutable because it is also borrowed as immutable
  --> src/main.rs:21:17
   |
18 |         match self.messages.get(0) {
   |               ------------- immutable borrow occurs here
...
21 |                 self.messages.push("empty list".to_string());
   |                 ^^^^^^^^^^^^^ mutable borrow occurs here
22 |             }
23 |         }
   |         - immutable borrow ends here

error: aborting due to previous error

#2

The borrow checker is not all that sophisticated. The process to make it smarter is called nonlexical borrows.

It decided that you wanted to borrow self.messages for the entire match statement, because you used self.messages.get. It is complaining as you are also mutably borrowing it within that statement with self.messages.push.


#3

Borrows are based on types and static scopes. The return type of self.messages.get(0) is an Option<&String>, which is a type that may contain a reference. Even in the None branch of the match, the borrow checker still believes that the return value may have a reference in it that would be invalidated by the push.

To get around that, you can restructure your code to break the dependency. Something like

let is_empty = match self.messages.get(0) {
    Some(_) => false,
    None => true
};
if is_empty {
    self.messages.push("empty list".to_string());
}

Or use a method that doesn’t potentially return a reference:

if self.messages.is_empty() {
    self.messages.push("empty list".to_string());
}