为什么Rust允许使用不可变绑定通过引用字段进行突变?

15

如果我将一个不可变的变量绑定到一个结构体上,Rust通常不允许我修改结构体的字段或拥有的子结构体的字段。

然而,如果该字段是一个可变引用,尽管我的绑定是不可变的,Rust仍然可以允许我对所引用的对象进行修改。

为什么会允许这种情况呢?这不是与Rust对不可变性的正常规则不一致吗?

Rust 不会 允许我通过一个不可变引用做同样的事情,因此不可变引用具有不同的行为方式。

代码示例:

struct Bar {
    val: i32,
}

struct Foo<'a> {
    val: i32,
    bar: Bar,
    val_ref: &'a mut i32,
}

fn main() {
    let mut x = 5;

    {
        let foo = Foo { 
            val: 6, 
            bar: Bar { val: 15 },
            val_ref: &mut x
        };

        // This is illegal because binding is immutable
        // foo.val = 7;

        // Also illegal to mutate child structures
        // foo.bar.val = 20;

        // This is fine though... Why?
        *foo.val_ref = 10;

        let foo_ref = &foo;

        // Also illegal to mutate through an immutable reference
        //*foo_ref.val_ref = 10;
    }

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

1
@Shepmaster 确实,那篇文章很相似 - 尽管我的问题更关注为什么 *foo.val_ref = 10; 是可接受的,而不是为什么其他的不是。在我看来,似乎我的示例中的任何东西都不应该通过不可变绑定被接受。 - something_clever
1个回答

14
简单来说,引用中的可变性和变量中的可变性是互相独立的。这两种可变性是相关的,因为我们只能从一个可变变量(或绑定)中借用可变内容。除此之外,在Rust中每个二进制组合都是可能的。
               reference mutability
            -----------------------------
variable   |     x: &T  |      x: &mut T |
mutability |------------+----------------|
           | mut x: &T  |  mut x: &mut T |
            -----------------------------

我们可以想到许多代码示例来说明变量x的用途。例如,一个不可变变量的可变引用可以修改其他元素,但不能修改自身:
let mut a = 5;
let mut b = 3;
let x: &mut i32 = &mut a;

*x = 10; // ok

x = &mut b; // nope! [E0384]
*x = 6;

即使作为结构体中的一个字段,这也不会影响Rust安全性的保证。如果一个变量被不可变地绑定到结构体值,那么每个字段也将是不可变的。例如:
let mut x = 5;
let foo = Foo { 
    val: 6, 
    bar: Bar { val: 15 },
    val_ref: &mut x
};
*foo.val_ref = 10;

这里没有对foo进行任何变异: foo.val_ref仍然指向x。前者可以被改变,因为它是可变借用。 引用是独立进行借用检查的。Foo中的生命周期参数'a使编译器能够跟踪借用情况。

第二个示例(如下所示)不起作用,因为从&Foo,我们只能检索到其字段的引用(例如val_ref: &mut i32)。反过来,为了防止别名,&&mut i32只能强制转换为&i32。不能通过不可变引用可变地借用数据。

let foo_ref = &foo;
*foo_ref.val_ref = 10; // error[E0389]

Rust不允许我通过一个不可变引用来做同样的事情。因此,不可变引用与不可变绑定具有不同的行为。
没错!
另请参阅:
- 在变量名之前和冒号后面放置"mut"有什么区别? - 如何从&&mut Foo中借用可变引用? - 可变借用自动更改为不可变?

5
这个内容在书的第一版中有涵盖,具体来说:y是对可变引用的不可变绑定,这意味着你不能将y绑定到其他东西(y = &mut z),但你可以改变绑定到y的东西(*y = 5)。这是一个微妙的区别。 - Simon Whitehead
我的整体困惑在于它似乎是不一致的 - 在某些情况下,可变性是传递的,在其他情况下则不是。 - something_clever
@E_net4 所以我想知道为什么决定不通过绑定来继承可变性。在我的例子中,我的感觉实际上是Rust不应该接受它们中的任何一个。也就是说 - 如果我的绑定是不可变的,即使我借用了可变的引用,我也不应该能够转换地改变引用。 - something_clever
@E_net4 很抱歉一直向您提问,但是您帮助我更好地理解了 :)不过我也发现有点奇怪的是,即使递归地访问(我不能修改我的不可变绑定的字段-字段-字段),非引用结构体成员的不可变性也是传递的。而且这并不涉及重新定位不可变变量... - something_clever
@something_clever 我已经考虑了这些问题并更新了答案。 - E net4
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接