Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is indexing a mutable vector based on its len() considered simultaneous borrowing?

I know the general answer — You can only borrow mutably once or immutably many times, but not both. I want to know why this specific case is considered simultaneous borrowing.

I have the following code:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let n = 3;
    // checks on n and v.len() and whatever else...
    let mut s = v[..n].to_vec();
    for i in 0..n {
        v[i + v.len() - n] = s[1];
    }
}

which produces the following error under 1.36.0:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:15
  |
7 |         v[i + v.len() - n] = s[1];
  |         ------^-----------
  |         |     |
  |         |     immutable borrow occurs here
  |         mutable borrow occurs here
  |         mutable borrow later used here

It seems that there is no way for the write to v[x] to happen until x is computed, by which time the immutable borrow will be complete. Since the ordering here is completely in series, why doesn't the compiler recognize the dependency and treat these as non-overlapping borrows? Put another way, is there any scenario where this could lead to an actual problem?

Marouane Fazouane suggested concurrency as a possibility, but I don't think this is the case. If there were another thread with a (presumably) mutable reference, it would be a violation to then call v.len(), or to start v[...]. Here, the compiler knows everything that's happening to v — it's a local definition with no other calls. For me, the question is why is this simultaneous borrowing when there's no way for v[] to happen until len() returns. It's akin to v.mutable_call(v.immutable_call());

Incidentally, an earlier version of the compiler (1.28) gave an error that indicated the close bracket as the end of the mutable borrow, so it seemed order is based on the source order, and since the source has the two intermingled, they could be considered overlapping. If so, surely the compiler could improve this...right?

This seems closely related to Why is there a borrow error when no borrowing overlap is occurring?

like image 387
jspencer Avatar asked Aug 08 '19 06:08

jspencer


1 Answers

If so, surely the compiler could improve this...right?

Indeed, NLL intentionally started conservatively, as explained in #494341.

Extracting the temporary allows it to compile:

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];
    let n = 3;
    // checks on n and v.len() and whatever else...
    let s = v[..n].to_vec();
    for i in 0..n {
        let index = i + v.len() - n;
        v[index] = s[1];
    }
}

This makes it clear that the issue is strictly one of not computing the index before attempting to use it.

Since it is not possible to start the call to IndexMut<Idx>::index_mut(&mut self, index: Idx) before computing the Idx, there is no reason to start the mutable borrow of v before computing the index.

1Courtesy of trentcl.

like image 99
Matthieu M. Avatar answered Nov 07 '22 00:11

Matthieu M.