【问题标题】:Why does order of mutable borrows matter in Rust? [duplicate]为什么可变借用的顺序在 Rust 中很重要? [复制]
【发布时间】:2020-10-12 03:14:36
【问题描述】:

此代码编译(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

此外,在 second_borrow 的实例化之后使用 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);
    
// ...
    
first_borrow.push(1);

考虑到文档似乎对范围所说的内容,这令人惊讶。在编译的代码中,为什么我们没有两个可变借用呢?

在编译的示例中,Rust 是否看到,在 let second_borrow = ... 之后,任何地方都不再提及 first_borrow,因此它取消借用 first_borrow 的可变借用,因此保留了一个在main()?! 的整个范围内单次借用?!

【问题讨论】:

  • “文档似乎说的范围”到底是什么?在旧版本的 Rust(pre-1.26,IIRC)中,一些这样的代码不会被编译。它现在可以编译。您可能正在阅读一些过时的文档吗?
  • @CoronA 几乎!谢谢。我的情况只是不同,我的可变借用似乎绑定到范围内仍然存在的对象(例如first_borrow),而在那个问题中,它们仅在范围内的函数调用中。这些案例在技术上可能是等效的,但我的案例对我来说更违反直觉。
  • 我推荐阅读current documentation,它解释了借用的行为。
  • 当前的文档确实解释了这一点:“请注意,引用的范围从引入它的位置开始,一直持续到最后一次使用该引用。”不幸的是,这是在许多使用 { } 大括号表示范围的范围示例的末尾,这让我有点困惑。

标签: rust mutable borrow-checker borrowing


【解决方案1】:

在编译的代码中,为什么我们没有两个可变借用呢?

简短的回答是同一条数据的两个可变借用不能同时在范围内,第一个示例不违反该限制。请注意,这是可变引用的“一个大限制”的必然结果,即“您只能对特定范围内的特定数据有一个可变引用”。请参阅 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 是否看到,在 let 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);
    }

当然第二个例子不能这样写,因为借用相互交叉。

【讨论】:

  • 导致变量“超出范围”的特性被称为“非词法生命周期”。 stackoverflow.com/questions/50251487/…
  • 之前,我读过一些old docs,可以追溯到使用大括号定义范围时不是可选的,就像现在一样!那也是我第一次了解借贷的时候,我的困惑源于不相信first_borrow 的范围可以在main() 关闭} 之前结束。
  • @AmigoNico 技术上是正确的,但是,由于新的借用检查器在其分析中根本不使用范围,我发现尝试描述此功能与“ old" 借用检查器确实做到了——这种解释只对已经熟悉 lexical 生命周期的人有意义。这是一个有趣的历史记录,但它没有解释力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-07
  • 1970-01-01
  • 2018-12-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多