函数参数中的可变借用

Mutable borrow in function argument

为什么以下代码无法编译 (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());
}

借款检查员抱怨说:

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

然而,代码是安全的,最后一行几乎是机械的转换使其可以编译 (playground):

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

据我了解,此类问题已通过非词汇生命周期得到解决。 是一个例子,还有很多。

是否有一些微妙之处使第一个变体毕竟不正确,所以 Rust 拒绝它是正确的?还是 NLL 功能还没有在所有情况下完成?

Rust 编译器首先评估调用对象,然后评估传递给它的参数。这就是为什么它首先借用h.insert,然后是h.remove。由于 h 已经为 insert 可变借用,它拒绝为 remove.

的第二次借用

使用下一代借阅检查器Polonius时,这种情况没有改变。您可以使用 nightly 编译器自己尝试一下:
RUSTFLAGS=-Zpolonius cargo +nightly run

求值顺序与 C++ 类似:https://riptutorial.com/cplusplus/example/19369/evaluation-order-of-function-arguments

你的问题也适用于一个可能更令人惊讶的相关案例——当方法参数需要 &mut self:

时,方法调用需要 &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());
}

Rust 借用检查器使用它所谓的两阶段借用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.

正如你所说,这是安全的,因为内部借用在外部借用开始之前完成,但实际上认识到此时在编译器中过于复杂。

另请参阅: