为什么我们可以将可变引用转换为不可变引用?

Eug*_*Loy 2 reference rust borrow-checker mutable-reference

来自铁锈书

在任何给定时间,您可以拥有一个可变引用或任意数量的不可变引用。

考虑以下代码:

fn main() {
    let mut a = 41;
    let b = &mut a;
    let c: &i32 = &a; // [1]
    let d: &i32 = &b;

    println!("{} {}", b, c); // [2]
    println!("{} {}", b, d);
}

Run Code Online (Sandbox Code Playgroud)

如果我们尝试编译我们会得到:

error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
  --> src\main.rs:43:19
   |
42 |     let b = &mut a;
   |             ------ mutable borrow occurs here
43 |     let c: &i32 = &a; // [1]
   |                   ^^ immutable borrow occurs here
44 |     let d: &i32 = &b;
   |                   -- mutable borrow later used here
Run Code Online (Sandbox Code Playgroud)

...因此,只有一个可变引用的规则得到了检验。

但是,如果您对标记为[1]和 的行进行注释[2],则一切都可以正常编译。请注意,在这种情况下bis 可变引用 amdd是不可变引用(似乎与 相同c)。

为什么我们允许这种情况,为什么这种情况编译时不会违反有关具有一个可变引用或 n 个不可变引用的规则?

dre*_*ato 6

在这里,本书进行了简化,以便更快地传达信息。实际上,您实际上可以拥有任意数量的可变引用。

let mut a = 41;
let b = &mut a;
let c = &mut *b;
f(c);
let d = &mut *c;
let e = c;
Run Code Online (Sandbox Code Playgroud)

实际的限制是只有一个可变引用可以处于活动状态。这也适用于原所有者。

因此,当c上面创建 时,您进入一个区域,您可以在其中传递c给诸如 之类的函数f,用于c创建诸如 之类的其他借用d,或者更改c诸如之类的所有权eb在最后一次c使用之前,您不能执行任何这些操作。而当你创建的时候,你就进入了另一个无法使用的d区域。c由于d从未使用过,因此该区域立即结束并c再次变为活动状态。

这应该是有道理的,因为如果没有它,可变引用将受到极大的限制。例如,每个采用可变引用的函数都会从fn f(x: &mut i32)您传递给它的内容中临时借用新的引用。如果没有,您将只能使用可变借用一次。

这解释了为什么您的原始代码不起作用。您正在尝试使用awhile bis active。

这是你的第二个版本:

fn main() {
    let mut a = 41;
    let b = &mut a;
    // let c: &i32 = &a; // [1]
    let d: &i32 = &b;

    // println!("{} {}", b, c); // [2]
    println!("{} {}", b, d);
}
Run Code Online (Sandbox Code Playgroud)

我将进行两项不会改变任何借用关系的更改。首先,let d: &i32 = &b;编译器自动将其扩展为let d: &i32 = &*b;. 如果没有,这将尝试将 a 分配&&mut i32给类型的变量&i32并且不会编译。其次,println!为了方便起见,宏添加了对其每个参数的引用。这是只有宏才能在无形中完成的事情;当函数引用时,它总是显式的。我println!用一个函数替换,以便您可以看到每个变量实际上是如何借用的。

fn main() {
    let mut a = 41;
    let b = &mut a;
    let d: &i32 = &*b;

    print(&b, &d);
}

fn print(x: &&mut i32, y: &&i32) {
    println!("{} {}", *x, *y);
}
Run Code Online (Sandbox Code Playgroud)

从我上面写的来看,这似乎不起作用,因为您正在使用b,然后d立即使用。但是可变引用(以及所有者和不可变引用)还可以做另一件事:生成任意数量的不可变引用。一旦给出一个不可变引用,您就进入了一个可以使用该不可变引用创建更多不可变引用的区域。在这种情况下,b创建一个对其内容的不可变引用(本质上&a,使用 type &i32)和一个对其自身的不可变引用(使用 type &&mut i32)。

考虑到这一点,上面的代码如下所示:

  • a被创建为所有者
  • a创建可变引用b并变得完全不活动
  • b创建不可变引用d并变得部分不活动
  • 另一个不可变的引用被创建到b其自身(仍然部分不活动)
  • 创建对其d自身的不可变引用
  • 这两个都传递给print

就在main结束之前,给定的两个引用print和引用d过期,b再次激活。b然后过期,a再次激活。然后a被丢弃,main结束。