如何在Rust中解释对可变类型的不可变引用?

时间:2019-05-06 12:21:23

标签: rust immutability borrow-checker mutability

如果我的取消引用链中存在任何个不可变的引用,则似乎无法对任何内容进行突变。样本:

fn main() {
    let mut x = 42;
    let y: &mut i32 = &mut x; // first layer
    let z: &&mut i32 = &y; // second layer
    **z = 100; // Attempt to change `x`, gives compiler error.

    println!("Value is: {}", z);
}

我遇到了编译器错误:

error[E0594]: cannot assign to `**z` which is behind a `&` reference
 --> src/main.rs:5:5
  |
4 |     let z: &&mut i32 = &y; // second layer
  |                        -- help: consider changing this to be a mutable reference: `&mut y`
5 |     **z = 100; // Attempt to change `x`, gives compiler error.
  |     ^^^^^^^^^ `z` is a `&` reference, so the data it refers to cannot be written

从某种意义上讲,这是有道理的,否则编译器将无法防止对同一变量具有多个可变的访问路径。

但是,在查看类型时,语义似乎是违反直觉的:

  • 变量y的类型为&mut i32,或者用简单的英语“对整数的可变引用”。
  • 变量z的类型为&&mut i32,或者用简单的英语“对可变整数的引用是不可变的”。
  • 通过一次取消引用z(即*z),我将得到类型&mut i32的东西,即与y类型相同的东西。但是,再次引用此 (即**z)可以得到i32类型的某种东西,但不允许我对该整数进行突变。

从本质上讲,某种程度上的引用对我来说是谎言,因为它们实际上并没有按照他们声称的去做。在这种情况下,我应该如何正确阅读引用类型,或者还应该如何恢复对该概念的信任?

对此示例进行测试:

fn main() {
    let mut x = 42;
    let y: &mut i32 = &mut x; // first layer
    let m: &&mut i32 = &y; // second layer
    let z: &&&mut i32 = &m; // third layer
    compiler_builtin_deref_first_layer(*z);
}

fn compiler_builtin_deref_first_layer(v: &&mut i32) {
    compiler_builtin_deref_second_layer(*v);
}

fn compiler_builtin_deref_second_layer(w: &mut i32) {
    println!("Value is: {}", w);
}

最后两个函数的参数类型正确。如果我更改其中任何一个,编译器都会抱怨类型不匹配。但是,如果按原样编译示例,则会出现此错误:

error[E0596]: cannot borrow `**v` as mutable, as it is behind a `&` reference

以某种方式,对compiler_builtin_deref_first_layer的调用似乎还可以,但对compiler_builtin_deref_second_layer的调用却不行。编译器错误涉及**v,但我只看到*v

1 个答案:

答案 0 :(得分:2)

  

从本质上讲,某种程度上的引用对我来说是谎言,因为它们实际上并没有按照他们声称的去做。在这种情况下,我应该如何正确阅读引用类型,或者还应该如何恢复对该概念的信任?

在Rust中读取引用的正确方法是获得权限。

如果不借用对象的所有权,则授予您对对象执行任何操作的权限;创建,销毁它,然后将其从一个地方移到另一个地方。您是所有者,可以执行您想做的事情,可以控制该对象的寿命。

可变引用从所有者那里借用了对象。当可变引用有效时,它授予对该对象的独占访问权限。没有人可以读取,写入对象或对该对象执行任何其他操作。可变引用也可以是调用和排他引用或排他借用。您必须将对象的控制权交还给原始所有者,但与此同时,您可以使用该对象执行任何操作。

不可变的引用或共享借用意味着您可以与其他人同时访问它。因此,您只能阅读它,而没有人可以对其进行修改,否则将根据操作发生的确切顺序产生不确定的结果。

可以对拥有的对象进行可变(或排他)引用和不可变(或共享)引用,但这并不意味着您在通过引用引用该对象时就拥有该对象。您可以通过哪种引用来限制对象的处理能力。

因此,不要将&&mut T引用视为“对T的可变引用的不可变引用”,然后再考虑“嗯,我不能改变外部引用,但我应该能够更改内部参考。”

而是将其视为“某人拥有T。他们已经授予了独占访问权限,所以现在有人有权修改T。但是与此同时,该人已授予对&mut T的共享访问权限,这意味着他们已承诺一段时间内不会对其进行更改,并且所有用户都可以使用对&mut T的共享引用,包括取消引用到基础T,但仅适用于您通常可以使用共享引用完成的事情,这意味着可以阅读但不能写作。”

最后要记住的是,可变或不可变部分实际上并不是引用之间的根本区别。实际上,这是排他性还是共享性。在Rust中,您可以通过共享引用来修改某些内容,只要有某种内部保护机制可以确保一次只有一个人这样做。可以采用多种方式,例如CellRefCellMutex

因此&T&mut T所提供的并不是真正的不变或可变的访问,尽管它们如此命名是因为这是它们在没有任何语言的情况下在语言级别提供的默认访问级别库功能。但是,它们真正提供的是共享访问或互斥访问,然后,数据类型的方法可以根据调用者是拥有值,专有引用还是共享引用来为调用方提供不同的功能。

因此将引用视为权限;而参考正是您从中获得帮助的基础,它决定了您可以使用它做些什么。而且,当您拥有所有权或排他性引用时,暂时提供排他性或共享引用会阻止您在那些借来的引用仍然存在的情况下可变地访问对象。