为什么可变借入的顺序在Rust中很重要?

问题描述

代码编译(playground link):

use std::collections::HashMap;

fn main() {
    let mut h = HashMap::<char,Vec<i32>>::new();
    h.insert('a',vec![0]);
    
    let first_borrow = h.get_mut(&'a').unwrap();
    first_borrow.push(1);
    let second_borrow = h.get_mut(&'a').unwrap();
    second_borrow.push(2);
}

使用借位(push()调用)更改代码顺序...

let first_borrow = h.get_mut(&'a').unwrap();
let second_borrow = h.get_mut(&'a').unwrap();
first_borrow.push(1);
second_borrow.push(2);

...使其无法编译:

error[E0499]: cannot borrow `h` as mutable more than once at a time
 --> src/main.rs:8:25
  |
7 |     let first_borrow = h.get_mut(&'a').unwrap();
  |                        - first mutable borrow occurs here
8 |     let second_borrow = h.get_mut(&'a').unwrap();
  |                         ^ second mutable borrow occurs here
9 |     first_borrow.push(1);
  |     ------------ first borrow later used here

此外,在first_borrow的实例化之后使用second_borrow也不会编译:

let first_borrow = h.get_mut(&'a').unwrap();
first_borrow.push(1);
let second_borrow = h.get_mut(&'a').unwrap();
second_borrow.push(2);
    
// ...
    
first_borrow.push(1);

考虑到文档中关于范围的说法,这令人惊讶。在编译的代码中,为什么我们那里也没有两个可变借项?

在编译的示例中,Rust是否看到let second_borrow = ...之后没有任何地方提到first_borrow,所以它借用 {{1 }},从而在first_borrow的整个范围内保留一次借用?!

解决方法

在编译的代码中,为什么我们那里也没有两个可变借项?

简短的答案是,同一数据的两个可变借位不能同时在范围内,并且第一个示例不违反该限制。请注意,这是可变引用的“一个大限制”的推论,即“您只能在特定范围内对特定数据段进行一个可变引用”。请参阅Rust编程语言中的References and Borrowing部分。

您的第一个示例之所以编译是因为first_borrowsecond_borrow进入作用域之前就超出了作用域。 “超出范围”与在其余范围中未引用的变量同义。我不了解底层细节,但这是我对这个示例的看法。

    // first_borrow comes into scope
    let first_borrow = h.get_mut(&'a').unwrap();
    first_borrow.push(1);
    // first_borrow goes out of scope
    // second_borrow comes into scope
    let second_borrow = h.get_mut(&'a').unwrap();
    second_borrow.push(2);
    // second_borrow goes out of scope

对于第二个未编译的示例,我们可以看到first_borrowsecond_borrow的范围是交叉的。

    // first_borrow comes into scope
    let first_borrow = h.get_mut(&'a').unwrap();
    // second_borrow comes into scope
    let second_borrow = h.get_mut(&'a').unwrap();
    // !!! both first_borrow and second_borrow are in scope now !!!
    first_borrow.push(1);
    // first_borrow goes out of scope
    second_borrow.push(2);
    // second_borrow goes out of scope

在编译的示例中,Rust是否看到在让second_borrow = ...之后,再也没有提及first_borrow了,所以它取消了first_borrow的可变借位,因此在main的整个范围内保留了一个借位()?!

有效,是的。我不认为这是无聊的。如上所述,我相信该术语是first_borrow超出范围。

例如,您本可以编写第一个这样的示例。

    {
        let first_borrow = h.get_mut(&'a').unwrap();
        first_borrow.push(1);
    }
    {
        let second_borrow = h.get_mut(&'a').unwrap();
        second_borrow.push(2);
    }

当然,第二个示例不能以这种方式编写,因为借用彼此交叉。