问题
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