Nested method calls with existing mutable references

Viewed 242

The following code compiles successfully:

let mut v = vec![1];
let r = &mut v;
r.push(r.len());

while this one fails:

let mut v = vec![1];
let r = &mut v;
r.push(v.len());

with error:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
    |
    |     let r = &mut v;
    |             ------ mutable borrow occurs here
    |     r.push(v.len());
    |            ^ immutable borrow occurs here
    |     r.push(r.len());
    |     - mutable borrow later used here
  • Why the first example compiles correctly? Is it because the same reference: r is used in the outer and inner calls? Or is it because it applies the RFC 2025, Two-Phase Borrows? Or something else?
  • Why the second example fails given that the first example succeeds? Why the RFC 2025, Two-Phase Borrows does not apply here?

I suspect that in the first example there are no errors because the compiler does not create intermediate references and it uses the same reference: r so that there are not multiple borrows. However, if that is the case, why the following code fails to compile

let mut v = vec![1];
let r = &mut v;
r.push({r.push(0);1});
2 Answers

In the second example, v is still mutably borrowed when you try to get its length. Vec::len takes &self, so getting its length would mean borrowing immutably while it's already borrowed mutably.

Accessing len() through r is still ok because you are borrowing through the same borrow, not borrowing it again.


Note that even the first example fails in Rust 1.30 and earlier (or 1.35 with 2015 edition), because it relies on NLL (non-lexical lifetimes). The problem with NLL is that it isn't completely intuitive to know what is allowed. Essentially, it means that borrows that don't outlive the lexical scope of the data are ok, and also several other intuitively correct cases. Your third example is one of the cases that is still not permited by NLL.

1st example:

let mut v = vec![1];
let r = &mut v;
r.push(r.len());

Without the 2-phase borrows the code will not compile because the outer call creates a reborrow of r: &mut *r, and the inner call a new immutable reborrow of the same value: &*r.

With 2-phase borrows, the first reborrow is converted to &mut2 *r and later activated when the second reborrow is out of scope.

2nd example:

let mut v = vec![1];
let r = &mut v;
r.push(v.len());

Even with the 2-phase borrows it does not compile.

The inner call causes a reborrow of v: &mut v that clashes with r.

3rd example:

let mut v = vec![1];
let r = &mut v;
r.push({r.push(0);0});

Even with the 2-phase borrows it does not compile.

The inner call requires a &mut2 reborrows of *r that is not allowed by the 2-phase borrows since the outer call already created a &mut2 reborrows of *r.

References:

Related