Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutable borrow in function argument

Why doesn't the following code compile (playground):

use std::collections::HashMap;

fn main() {
    let mut h: HashMap<u32, u32> = HashMap::new();
    h.insert(0, 0);
    h.insert(1, h.remove(&0).unwrap());
}

The borrow checker complains that:

error[E0499]: cannot borrow `h` as mutable more than once at a time
 --> src/main.rs:6:17
  |
6 |     h.insert(1, h.remove(&0).unwrap());
  |     - ------    ^ second mutable borrow occurs here
  |     | |
  |     | first borrow later used by call
  |     first mutable borrow occurs here

The code is safe, however, and an almost mechanical transformation of the last line makes it compile (playground):

    //h.insert(1, h.remove(&0).unwrap());
    let x = h.remove(&0).unwrap();
    h.insert(1, x);

It was my understanding that this kind of issue got resolved with non-lexical lifetimes. This question is an example, and there are many others.

Is there some subtlety that makes the first variant incorrect after all, so Rust is correct to refuse it? Or is the NLL feature still not finished in all cases?

like image 239
user4815162342 Avatar asked Mar 14 '20 18:03

user4815162342


1 Answers

Your question also applies for a related case that may be more surprising — having the method call require &self when the method argument requires &mut self:

use std::collections::HashMap;

fn main() {
    let mut h: HashMap<u32, u32> = HashMap::new();
    h.insert(0, 0);
    h.contains_key(&h.remove(&0).unwrap());
}

The Rust borrow checker uses what it calls two-phase borrows. An edited transcription of a chat I had with Niko Matsakis:

The idea of two-phase borrows is that the outer &mut is treated like an & borrow until it is actually used, more or less. This makes it compatible with an inner & because two & mix, but it is not compatible with an inner &mut.

If we wanted to support that, we'd have had to add a new kind of borrow -- i.e., an "unactivated" &mut wouldn't act like an &, it would act like something else (&const, maybe... "somebody else can mutate")

It's less clear that this is OK and it seemed to add more concepts so we opted not to support it.

As you stated, this is safe because the inner borrow is completed before the outer borrow starts, but actually recognizing that in the compiler is overly complex at this point in time.

See also:

  • Nested method calls via two-phase borrowing
  • Two-phase borrows in Guide to Rustc Development
  • Cannot borrow as immutable because it is also borrowed as mutable in function arguments
like image 107
Shepmaster Avatar answered Oct 14 '22 22:10

Shepmaster