如何强制在Rust中将结构的字段始终保持不变?

LP_*_*LP_ 10 immutability rust

在Rust中,您无需在内指定可变性struct,但它是从变量绑定继承的。很好,但是即使根是可变的,也可以强制字段始终不变吗?

像这样的假设语法:

struct A {
    immut s: Shape, // immutable by design
    bla: Bla, // this field inheriting (im)mutability
}
let mut a = make_a();
a.s = x/*...*/; // illegal
Run Code Online (Sandbox Code Playgroud)

就像Java一样,这将有助于在程序中保持良好的语义限制final(以非常有限的方式)。

同样,我们可以想象这种struct利用内部不变性对内部不变数据进行非所有权引用的情况...

Dan*_*ath 11

具有单个字段的不变性是不可能的。在古代版本的Rust中(这是在0.8之前考虑的),这是一个选项,但由于规则使很多人感到困惑,因此将其删除。您可能会问,这令人困惑吗?这样想:如果一个字段被声明为可变的,而struct被声明为可变的,并且使用的引用是一个不变的引用(&),则该字段为_______

正如Lily Ballard指出的那样,最好的方法是,您可以将Shape字段声明为私有字段,并使用进行getter方法impl A {...}

mod inner {
    pub struct A {
        s: i32, // can't be seen outside of module
        pub bla: i32,
    }

    impl A {
        pub fn new() -> Self {
            Self { s: 0, bla: 42 }
        }

        pub fn get_s(&self) -> i32 {
            self.s
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
let mut a = inner::A::new();
a.s = 42; // illegal
println!("{}", a.s); // also illegal
println!("{}", a.get_s()); // could be made to serve as a read-only method
Run Code Online (Sandbox Code Playgroud)
mod inner {
    pub struct A {
        s: i32, // can't be seen outside of module
        pub bla: i32,
    }

    impl A {
        pub fn new() -> Self {
            Self { s: 0, bla: 42 }
        }

        pub fn get_s(&self) -> i32 {
            self.s
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

有一种提议可能会完全放弃可变性和不变性的概念(您不能说一个结构永远不会改变)。有关更改,请参见Niko的说明


Lil*_*ard 5

您不能在字段上强制不变性。必要时该结构将如何改变其自身的值?

您可以做的是将字段设为私有,并公开getter方法以返回对该字段的引用(或复制/克隆值)。

  • 能够突变数据并不总是很有意义。例如,您听说过函数式编程吗?Java`final`修饰符可能被认为是无用的,因为简单的getter可以达到相同的效果。这仅仅是使它变得容易(使人们更自然地完成它-避免代码中无用的潜在状态总会很好)和变得更加清晰(自动记录值永远不会改变是有用的)的问题。 (10认同)
  • @DavidCallanan这实际上取决于结构是否必须在其字段上维护类型系统未强制执行的不变量。例如,“Vec”有一个私有字段“len”和一个公共 getter“fn len(&self)”。改变“len”字段会破坏“Vec”的安全性,因此它必须保持只读状态。 (2认同)

dan*_*iel 5

您可以创建一个结构体并且只为它实现 Deref 特性。没有 DerefMut 特性,包含的值不可能发生变异。

https://doc.rust-lang.org/std/ops/trait.Deref.html

这样,编译器将使成员可用,就好像它没有包装在另一个结构中一样,不需要任何编写的 getter 方法调用。

use std::ops::Deref;

/// A container for values that can only be deref'd immutably.
struct Immutable<T> {
    value: T,
}

impl<T> Immutable<T> {
    pub fn new(value: T) -> Self {
        Immutable { value }
    }
}

impl<T> Deref for Immutable<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
Run Code Online (Sandbox Code Playgroud)
struct Foo {
    bar: Immutable<Vec<u8>>,
    baz: usize,
}

impl Foo {
    pub fn new(vec: Vec<u8>) -> Self {
        Foo {
            bar: Immutable::new(vec),
            baz: 1337,
        }
    }

    pub fn mutate(&mut self) {
        self.bar.push(0); // This will cause a compiler error
    }
}
Run Code Online (Sandbox Code Playgroud)
|
|         self.bar.push(0);
|         ^^^^^^^^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `runnable::immutable::Immutable<std::vec::Vec<u8>>`
Run Code Online (Sandbox Code Playgroud)

  • 如果您最终使用这种方法,请记住为“Immutable&lt;T&gt;”实现与“T”相同的特征,否则库的用户在尝试使用“Immutable&lt;T&gt;”作为‘T’。[参见此示例](https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2018&amp;gist=fe86eeacf749a2efa41b499107cfbedd) (2认同)