2

I'm having trouble understanding why I can't use v a second time, when it seems that the first mutable borrow has gone out of scope:

fn get_or_insert(v: &mut Vec<Option<i32>>, index: usize, default: i32) -> &mut i32 {
    if let Some(entry) = v.get_mut(index) { // <-- first borrow here
        if let Some(value) = entry.as_mut() {
            return value;
        }
    }

    // error[E0502]: cannot borrow `*v` as immutable because it is also borrowed as mutable    
    while v.len() <= index {                // <-- compiler error here
        v.push(None);
    }

    // error[E0499]: cannot borrow `*v` as mutable more than once at a time
    let entry = v.get_mut(index).unwrap();  // <-- compiler error here
    *entry = Some(default);
    entry.as_mut().unwrap()
}

playground link

Do I have my variable scopes wrong, or is the borrow checker protecting me from something I'm not seeing?

Edit: the error message with NLL enabled is pretty good:

error[E0502]: cannot borrow `*v` as immutable because it is also borrowed as mutable
  --> src/main.rs:10:11
   |
3  | fn get_or_insert(v: &mut Vec<Option<i32>>, index: usize, default: i32) -> &mut i32 {
   |                     - let's call the lifetime of this reference `'1`
4  |     if let Some(entry) = v.get_mut(index) {
   |                          - mutable borrow occurs here
5  |         if let Some(value) = entry.as_mut() {
6  |             return value;
   |                    ----- returning this value requires that `*v` is borrowed for `'1`
...
10 |     while v.len() <= index {
   |           ^ immutable borrow occurs here
Tim Robinson
  • 53,480
  • 10
  • 121
  • 138
  • enable nll give a more precise error; https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015 – Stargateur Nov 13 '18 at 18:47
  • 1
    I can't explain to you why it doesn't work, but here a solution, https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=e3c64132b2065080b23330b67d94da86. I wonder if we could add this to std, but I don't see any use case of this on a Vec. I think a hashmap would be more suitable for this kind of thing but without know all detail from your project it's impossible to be sure. https://doc.rust-lang.org/std/collections/hash_map/enum.Entry.html#method.or_insert – Stargateur Nov 13 '18 at 19:06
  • 1
    Possible duplicate of [Double mutable borrow error in a loop happens even with NLL on](https://stackoverflow.com/questions/50519147/double-mutable-borrow-error-in-a-loop-happens-even-with-nll-on) – Sven Marnach Nov 13 '18 at 19:42
  • Also related: [If let borrow stays after return even with #!\[feature(nll)\]](https://stackoverflow.com/q/53034769/279627) – Sven Marnach Nov 13 '18 at 19:43
  • You aren't missing anything here. Your code is actually safe, but the borrow checker can't handle this case. The type checker infers a _single_ lifetime for the first borrow, and that lifetime must be long enough to return `value`. There aren't two different lifetimes for both branches of the `if let`. – Sven Marnach Nov 13 '18 at 19:46
  • Note that your while loop terminates once `v.len() == index`, which is one iteration too early. Stargateur's code fixes this issue as well. – Sven Marnach Nov 13 '18 at 21:06
  • Thanks all, I’ll try @Stargateur’s solution in the larger program and report back. I’m working on a fast ordered map where the frequently accessed elements at one end is backed by a vector (actually a VecDeque) and the rest are kept in a BTreeMap, The above code comes from attempting to implement a HashMap-style entry API. – Tim Robinson Nov 13 '18 at 22:22

1 Answers1

1

The key point is that, even with NLL, the lifetime of the return value spans the whole function. The fact that the function returns early on line 4 is not taken into account when deciding whether the v reference is accessible in the code lower down.

The fix suggested by @Stargateur is to grow the vector if needed before accessing an element:

fn get_or_insert(v: &mut Vec<Option<i32>>, index: usize, value: i32) -> &mut i32 {
    if v.len() < index {
        v.resize(index + 1, None);
    }
    v[index].get_or_insert(value)
}

playground link

Here's where I used the technique in the final code.

Tim Robinson
  • 53,480
  • 10
  • 121
  • 138