为什么我可以使用 &mut (**ref) 创建两个实时 &mut 到同一个变量?

JMC*_*JMC 2 rust borrow-checker

考虑以下两个代码片段:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let mut borrower2 = &mut foo;  // error: cannot borrow `foo` as mutable more than once at a time

    *borrower2 = 2;
    *borrower = 3;

    println!("{}", foo);
}
Run Code Online (Sandbox Code Playgroud)

这不会像预期的那样编译,因为不能对同一变量有两个可变引用。但是,如果我按如下所示替换有问题的行,它就会起作用:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;
    let borrower2 = &mut (**rr);
    
    *borrower2 = 2; // I can use both of these to mutate foo 
    *borrower = 3; // Shouldn't these borrows clearly overlap? 

    println!("{}", foo);
}
Run Code Online (Sandbox Code Playgroud)

当颠倒两个赋值时,会出现以下错误:

error[E0506]: cannot assign to `*borrower` because it is borrowed
  --> src/main.rs:48:5
   |
45 |     let rr = &mut borrower;
   |              ------------- `*borrower` is borrowed here
...
48 |     *borrower = 3;
   |     ^^^^^^^^^^^^^ `*borrower` is assigned to here but it was already borrowed
49 |     *borrower2 = 2;
   |     -------------- borrow later used here
Run Code Online (Sandbox Code Playgroud)

Bla*_*ans 6

是的,确实,如果 Rust 没有您可能已经知道的任何其他机制,这将是一个错误。然而,正如您可能已经猜到的那样,在重新借用的情况下还有一条附加规则。实际上很难找到关于此功能的正确文档,快速而肮脏的谷歌搜索让我找到的最好的就是这个。我认为这是因为堆叠借用是一个年轻的想法,相关工具仍然是 WIP(例如 miri),因此文档也很新鲜。

再借款与堆积借款密切相关。这个想法是,在第二个示例中,不仅&mut **r是对 的全新借用,而且是通过 进行的借用。因此,Rust 会认为你失效的时间是有效的,一旦结束,又开始有效。foorr&mut **r&mut **rr

考虑这个问题的一个好方法是将每个分配(为了简单起见)视为一个堆栈。每次对分配中包含的数据进行可变借用时,都会将该借用添加到堆栈中。当然,您不能同时获取两个可变借用,因此当您创建新的可变借用时堆栈必须为空。但是,如果您有一个活动的可变借用,您可以通过将其推入堆栈顶部来从该独占借用创建一个新的可变借用。请注意,您始终只能使用活动的可变借用,即堆栈顶部的借用。当借用的生命周期结束时,它将从堆栈中弹出。重要的是,您不能弹出不在堆栈顶部的内容,这意味着:重借最多必须与其父借借一样长。违反这条规则就是 UB,并且是 miri 检查的(其中之一)。

事实上,当您允许将分配或可变借用拆分为不重叠的可变借用(如 with )时,事情可能会变得更加时髦split_at_mut。在这种情况下,借用不会像堆栈一样生长,而是像一棵树一样,您只能“弹出”叶子,并且只能将叶子添加到现有叶子上......

  • 重新借用先于堆叠借用,并且与其无关。不过,两者都是建立在堆叠借用的想法之上的。 (2认同)
  • 首次创建重新借用时,堆叠借用并不存在。那时,只有编译器允许的内容,以及您需要在不安全代码中保留的模糊约束。堆叠借用使(其中一些)这些约束变得清晰和精确。它还描述了内存模型以及编译器可以执行哪些优化。这确实意味着再借用要遵守其规则,否则语言就会不健全;但它们并不是围绕它设计的。而且 Stacked Borrows 甚至不是 Rust 的官方内存模型。 (2认同)