如何强制struct的字段在Rust中始终是不可变的?

时间:2014-05-19 17:16:44

标签: 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

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

另外,我们可以想象这种struct对内部不可变数据有一些非拥有的引用,利用这种不变性......

5 个答案:

答案 0 :(得分: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
    }
}
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
    }
}
|
|         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>>`

答案 1 :(得分:4)

单个领域的不可变性是不可能的。这是古代version of Rust中的一个选项(在0.8之前考虑),但由于规则混淆了很多人,因此它被删除了。你可能会问,这有多令人困惑?想想这样:如果一个字段被声明为可变,并且struct被声明为可变,并且使用的引用是一个不可变引用(&),则该字段为_______

最好的是Lily Ballard noted,您可以将Shape字段声明为私有字段,并使用impl A {...}制作一个getter方法。

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
        }
    }
}
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
error[E0616]: field `s` of struct `main::inner::A` is private
  --> src/main.rs:20:5
   |
20 |     a.s = 42; // illegal
   |     ^^^

error[E0616]: field `s` of struct `main::inner::A` is private
  --> src/main.rs:21:20
   |
21 |     println!("{}", a.s); // also illegal
   |                    ^^^

有一个命题可能会完全忽略可变性和不变性的概念(你不能说结构永远不会改变)。有关此更改,请参阅Niko's explanation

答案 2 :(得分:3)

你不能强迫场上的不变性。结构如何在必要时改变自己的值?

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

答案 3 :(得分:0)

解决方案可能是采用更通用的方法:

pub struct Immutable<T> {
    value : T ,
}

impl<T> Immutable<T> {

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

    pub fn get( &self) -> &T { &self.value }
}

现在可以在每种情况下都将Immutable结构用于其他类型。

将其提供给模块可避免更改不可变对象的内容。仍然可以通过用新的Object覆盖持有Immutable对象本身的变量来更改它,但是您应该通过Immutable :: new语句注意到它,因此可以避免使用它。

答案 4 :(得分:0)

在许多情况下,关联常量会实现所需的行为:

struct Foo { blah: Blah }

impl Foo {
    const S: Shape = Shape { x: 1, y: 1 };
}

当然,常量不能在创建时设置,它们是在编译时设置的。如果需要动态性,可以按照其他答案中的说明将字段设为私有。