2

In the below code example, I'm trying to increment the member variable a of the struct X via a mutable reference to it, in four different ways. Here, the compiler gives the following error for the line denoted by B:

error[E0502]: cannot borrow `*x` as immutable because it is also borrowed as mutable
  --> src\main.rs:17:23
   |
17 |     *x.get_a_mut() += x.get_a(); //B DOESN'T COMPILE
   |     ------------------^--------
   |     ||                |
   |     ||                immutable borrow occurs here
   |     |mutable borrow occurs here
   |     mutable borrow later used here

If it is a problem to use a mutable and an immutable reference to a in the same expression, why does C and D compile?

struct X {
    a: i64,
}

impl X {
    pub fn get_a_mut(&mut self) -> &mut i64 {
        return &mut self.a;
    }

    pub fn get_a(&self) -> &i64 {
        return &self.a;
    }
}

fn my_fn(x: &mut X) {
    *x.get_a_mut() += 5; //A
    *x.get_a_mut() += x.get_a(); //B DOESN'T COMPILE
    *x.get_a_mut() += 2 * x.get_a(); //C
    *x.get_a_mut() = x.get_a() + x.get_a(); //D
}

fn main() {
    let mut x = X { a: 50 };
    my_fn(&mut x);
}
andreasdr
  • 3,804
  • 4
  • 28
  • 32
  • 1
    I ***guess*** it happens because `*x.get_a_mut() += 2 * x.get_a(); //C` is transformed to something like `let temp = 2 * x.get_a(); *x.get_a_mut() += temp;` thus `x` is no longer immutably borrowed when `x.get_a_mut()` is called; The same thing applies for `D` – Svetlin Zarev Aug 22 '21 at 08:58

1 Answers1

9

According to += documentation, you are calling something like add_assign(lhs: &mut i64, rhs: &i64) in case B and something like add_assign(lhs: &mut i64, rhs: i64) in cases A, C and D.

In case A, rhs is a constant, different from x.a; no problem.

In case C, rhs is a temporary (the result of 2 * x.get_a()) and does not need to keep a reference on x.a to exist; no problem.

In case D, rhs is a temporary (the result of x.get_a() + x.get_a()) and does not need to keep a reference on x.a to exist; no problem.

But when it comes to case B, rhs is a reference on x.a; then this call uses both a mutable (lhs) and immutable (rhs) reference on the same data (x.a) at the same time, which is forbidden.

You could eventually clone rhs: *x.get_a_mut() += x.get_a().clone().

prog-fh
  • 13,492
  • 1
  • 15
  • 30
  • Thanks, that makes it clear. So it is only in the context of the sub-scope of the `+=` operator that there is a problem in terms of the borrowing rules. There is no problem with mixing mutable and immutable references in one and the same expression (as in examples `C` and `D`). – andreasdr Aug 22 '21 at 10:08
  • 1
    @andreasdr I wouldn't go so far as to say that "there is no problem" - there are still cases where the current borrow checker rejects otherwise valid code, such as [this](https://stackoverflow.com/questions/60686259/mutable-borrow-in-function-argument) or [this](https://stackoverflow.com/questions/38023871/returning-a-reference-from-a-hashmap-or-vec-causes-a-borrow-to-last-beyond-the-s). The good news is that in most cases, and especially when dealing with `Copy` types, you can easily resolve the issue with an explicit variable or a call to `.clone()`. – user4815162342 Aug 23 '21 at 12:19