Mutable borrow in function argument

风流意气都作罢 提交于 2021-01-03 06:41:35

问题


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?


回答1:


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



回答2:


The Rust compiler first evaluates the calling object, then the arguments passed to it. That's why it first borrows the h.insert, then h.remove. Since the h is already borrowed mutably for insert, it denies the second borrow for remove.

This situation is not changed when using Polonius, a next-generation borrow checker. You can try it yourself with the nightly compiler:
RUSTFLAGS=-Zpolonius cargo +nightly run

The order of evaluation is similar in C++: https://riptutorial.com/cplusplus/example/19369/evaluation-order-of-function-arguments



来源:https://stackoverflow.com/questions/60686259/mutable-borrow-in-function-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!