请考虑以下两个代码片段:

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);
}

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

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);
}

在颠倒这两个分配时,会出现以下错误:

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

推荐答案

Disclaimer: not quite satisfied with the answers so far. I think a more in-depth answer would be more valuable.

Ownership & Borrowing

Rust中处理值(和引用)的两个基石概念是所有权和borrow :

  • 所有权可以转移:值从一个变量移到另一个变量.
  • 所有权可以暂时"借出":创建一个对值的引用(可变或不可变),借入它.

也可以克隆(或复制)一个值,在这种情况下,将创建一个独立于原始值的new值.在这种情况下,不会发生所有权转移,并且在新值可用时,对克隆(或复制)的值的borrow 结束.

复制引用

不可变(共享)引用是可复制的(它们实现了Copy特征),因为您可以在任何时刻拥有对给定值的任意数量的不可变引用.

但是,可变(唯一)引用不是唯一的,否则它们就不是唯一的.

这意味着将引用赋给变量将具有不同的属性,具体取决于它是否是不可变的:

let x = 3;
let y = &x;
let z = y; //  z is a Copy of y

let mut a = 7;
let b = &mut a;
let c = b; //  b is _moved_ to c, the variable b cannot be used afterwards.

后者是temporarily能够从bborrow a的引用(或可变引用)的关键动机,例如,进行函数调用:

fn foo(r: &mut i32) {
    *r += 1;
}

fn main() {
    let mut a = 7;
    let b = &mut a;

    foo(b);

    println!("{b}"); // Expected error, `b` was moved out.
}

录入再借款

上述问题的解决方案是re-borrowing的概念,即暂时borrow reference中的referred值.

关键的 idea 是,从一个价值中借款是very similarly%的效果.当您从某个值借入时:

  • 您borrow 的变量是完好无损的.
  • 在可变borrow 的情况下,该变量在borrow 的生存期(引用生存期的子集)内是不可访问的.

因此,再借款可以实现同样的关键理念:

  • 您borrow 的参照是完好无损的.
  • 在borrow 的整个生命周期内,您borrow 的引用是不可访问的--在可变borrow 的类型中.

再借入的句法是bit的特殊之处.您不能写&reference,因为这会创建对引用的引用,这是有效的,但不完全是您想要的.因此,您只需要编写&*reference来表明您希望引用reference本身所指的内容.

由于这种语法相当冗长,而且不太符合人体工程学,因此通常会为您完成automatically次重复borrow .以我们前面的例子为例:

fn foo(r: &mut i32) {
    *r += 1;    //  Re-borrow 1
}

fn main() {
    let mut a = 7;
    let b = &mut a;

    foo(b);     //  Re-borrow 2

    println!("{b}");
}

从底部开始,当使用可变引用调用接受可变引用的函数时,编译器自动插入&mut *,因此实际上示例not确实会导致编译器错误,即使b不能被复制,只能被移动.

同样,这*r += 1实际上是在再借款.这一次*是手动插入的,编译器然后隐式应用&mut,因为它将表达式重写为(*r).add_assign(1),其中add_assign&mut self.

再贷款无处不在,只是它是如此的自动,你可能从来没有注意到.

申请再借款

让我们判断一下您的示例,看看到底是怎么回事:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;          //  (1)
    let borrower2 = &mut (**rr);     //  (2)
    
    *borrower2 = 2;                  //  (3)
    *borrower = 3;                   //  (4)

    println!("{}", foo);
}

按顺序:

  1. Creates a mutable reference to a mutable reference to foo:
    • rr&mut &mut i32类型的.
    • 现在借了borrower美元.
  2. Create a mutable reference to foo:
    • borrower2&mut i32类型的.
    • rr现在是reborrowed.
  3. 赋值为borrower2所指的值.
  4. 赋值为borrower所指的值.

为什么允许(4)?

简短的回答是,借入rrborrower2的生命周期 结束,借入borrowerrr的生命周期 结束,因此borrower不再被借入.

长长的答案是,originally Rust编译器将考虑在其引用的生存期内borrow 变量,该生存期通常直到创建该引用的作用域的末尾.这是不灵活的,需要引入额外的作用域:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    {
        let rr = &mut borrower;          //  (1)
        let borrower2 = &mut (**rr);     //  (2)
    
        *borrower2 = 2;                  //  (3)
    }

    *borrower = 3;                   //  (4)

    println!("{}", foo);
}

为了让我们的工作更轻松,实现了非词法生存期(NLL),这给了编译器结束借入earlier的自由,而不是创建借入的引用范围的结束.一般来说,这意味着借入只持续到引用的第last use位.

因此,您的示例无需(手动)额外作用域即可工作.

如果你把作业(job)颠倒过来呢?即:

fn main() {
    let mut foo = 1;
    let mut borrower = &mut foo;
    
    let rr = &mut borrower;          //  (1)
    let borrower2 = &mut (**rr);     //  (2)
    
    *borrower = 3;                   //  (A)
    *borrower2 = 2;                  //  (B)

    println!("{}", foo);
}

好吧,那么NLL不会再拯救你了,因为它只收缩了借款的tail,而不是head.它could理论上只从第一次使用到最后一次使用,而不是从创作到最后一次使用,但它没有,因此:

  • borrower2最后一次用在(B).
  • 因此,必须从(2)到(B)borrow rr.
  • 因此,必须从(1)到(B)borrow borrower.
  • 因此,borrower是在(A)处借入的--再次try 再次借入是错误的.

安全了吗?

Rust相关问答推荐

如何访问Rust存储值的内存地址

是否有可能同时避免不兼容的不透明类型和代码重复?

有没有办法避免在While循环中多次borrow `*分支`

允许 rust 迹 struct 条目具有多种类型

如何设置activx websocket actorless的消息大小限制?

Nom 解析器无法消耗无效输入

Rust 中多个 & 符号的内存表示

Rust 如何返回大类型(优化前)?

在Rust中实现Trie数据 struct 的更好方式

全面的 Rust Ch.16.2 - 使用捕获和 const 表达式的 struct 模式匹配

有什么办法可以追踪泛型的单态化过程吗?

如何在 Emacs Elisp 中获得类似格式化的 LSP?

如何递归传递闭包作为参数?

Rust/Serde/HTTP:序列化`Option`

为什么 i32 Box 类型可以在 Rust 中向下转换?

没有通用参数的通用返回

Rust HRTB 是相同的,但编译器说一种类型比另一种更通用

如何在 Rust 的泛型函​​数中同时使用非拥有迭代器和消费迭代器?

基于名称是否存在的条件编译

如何从 Rust 应用程序连接到 Docker 容器中的 SurrealDB?