Cannot borrow as immutable because it is also borrowed as mutable in function arguments

6.5k views Asked by At

What is going on here (playground)?

struct Number {
    num: i32
}

impl Number {
    fn set(&mut self, new_num: i32) {
        self.num = new_num;
    }
    fn get(&self) -> i32 {
        self.num
    }
}

fn main() {
    let mut n = Number{ num: 0 };
    n.set(n.get() + 1);
}

Gives this error:

error[E0502]: cannot borrow `n` as immutable because it is also borrowed as mutable
  --> <anon>:17:11
   |
17 |     n.set(n.get() + 1);
   |     -     ^          - mutable borrow ends here
   |     |     |
   |     |     immutable borrow occurs here
   |     mutable borrow occurs here

However if you simply change the code to this it works:

fn main() {
    let mut n = Number{ num: 0 };
    let tmp = n.get() + 1;
    n.set(tmp);
}

To me those look exactly equivalent - I mean, I would expect the former to be transformed to the latter during compilation. Doesn't Rust evaluate all function parameters before evaluating the next-level-up function call?

1

There are 1 answers

0
wimh On BEST ANSWER

This line:

n.set(n.get() + 1);

is desugared into

Number::set(&mut n, n.get() + 1);

The error message might be a bit more clear now:

error[E0502]: cannot borrow `n` as immutable because it is also borrowed as mutable
  --> <anon>:18:25
   |
18 |     Number::set(&mut n, n.get() + 1);
   |                      -  ^          - mutable borrow ends here
   |                      |  |
   |                      |  immutable borrow occurs here
   |                      mutable borrow occurs here

As Rust evaluates arguments left to right, that code is equivalent to this:

let arg1 = &mut n;
let arg2 = n.get() + 1;
Number::set(arg1, arg2);

Editor's note: This code example gives an intuitive sense of the underlying problem, but isn't completely accurate. The expanded code still fails even with non-lexical lifetimes, but the original code compiles. For the full description of the problem, review the comments in the original implementation of the borrow checker

It should now be obvious what is wrong. Swapping those first two lines fixes this, but Rust does not do that kind of control-flow analysis.

This was first created as bug #6268, now it is integrated into RFC 2094, non-lexical-lifetimes. If you use Rust 1.36 or newer, NLL is enabled automatically and your code will now compile without an error.

See also: