特征上的不可变数据

时间:2017-01-12 16:06:09

标签: rust traits

我尝试使用返回某些数据的方法来定义特征,但impl不能更改此数据,即它只能设置一次并在生命周期内保留该值impl的。{有什么方法可以确保吗?

以下是我如何在C#中完成此操作,以供参考:

public abstract class Foo 
{
    private readonly uint number;

    public Foo(uint number) { this.number = numbers; }

    public uint GetNumber() { return number; }

}

1 个答案:

答案 0 :(得分:2)

您的问题的简短回答是。没有办法以类似于C#方法的方式实现这一点。幸运的是,Rust提供了比C#更好的可控性控制。

了解Rust中不变性的工作原理以及它与C#和Java等语言的区别非常重要。

C#中的不变性

class Foo {
   readonly Bar bar = new Bar();
   uint lives;
}

有些注意事项:

  • 不变性是按字段定义的。
  • 不变性很浅。例如,即使对bar的引用是不可变的,bar引用的值仍然是可变的。
  • C#中的不变性很容易被反射破坏。有edge cases即使没有反思也可以被颠覆。

Rust的不变性

struct Foo {
    bar: Bar,
    lives: u32
}

首先要注意的是结构定义没有说明其字段的不变性。这是因为没有像生锈中的场级变异那样的东西。 Rust中的可变性在绑定上定义为值:

// Declare an immutable binding to a Foo
let foo = Foo { bar: Bar::new(), lives: 10 };

// Attempting to mutate the value that foo points to is a compile error
foo.lives = 5; // compile error!

foo.bar.baz = 6; // Also a compile error, foo is deeply immutable

// We can redefine the binding to be mutable
let foo = mut foo; // foo is now mutable!

foo.lives = 5; // mutating foo here would be valid
foo.bar.baz = 6; // this is also valid, foo is deeply mutable

正如您所看到的,Rust中的可变性比C#更简单,更简洁:它与一个值的绑定决定了它是否可变,并且它是深度可变的或者非常不可改变的 *

尽管如此,让我们尝试在Rust中为您的问题建模。

首先,我们使用等效的GetNumber()方法定义特征:

trait Bar {
    fn number(&self) -> u32;
}

由于number()self采用不可变绑定,因此任何实现Bar的类型都无法通过调用number()来改变自身:

struct Foo {
    number: u32,
    oranges: u32
}

impl Bar for Foo {
    fn number(&self) -> u32 {
        self.number += 1; // Compile error. We have an immutable binding to self
        self.number
    }
}

正如您所看到的,控制Rust中的可变性就是控制绑定的定义方式。

让我们为我们的特征引入一种方法,该方法定义与self的可变绑定,并在Foo上更新我们的实现:

trait Bar {
    fn number(&self) -> u32;
    fn inc_oranges(&mut self);
}

impl Bar for Foo {
    fn number(&self) -> u32 {
        self.number
    }

    fn inc_oranges(&mut self) {
        // We have a mutable reference to self. We can mutate any part of self:
        self.oranges += 1;
        self.number += 1; // We can *also* mutate number
    }
}

在这里你可能会开始支持C#方法:在C#中,你可以将number声明为只读字段,同时让oranges保持可变,但是在Rust中,如果一个特征声明了一个可变的绑定到selfself的任何部分都可以变异。幸运的是,有一种解决方法。

* 内部可变性

Rust通过cell模块提供了一种改变值的方法,这些值是不可变绑定的一部分。总而言之,这些类型允许突变,同时仍然适应使用不可变绑定所提供的保证。它通过将原本可能的编译时(零成本)检查移动到运行时检查来完成此操作。

现在让我们把它们放在一起:

struct Foo {
    number: u32,
    orange: Cell(u32) // allow mutation via an immutable binding
}

trait Bar {
    fn number(&self) -> u32;
    fn inc_oranges(&self); // self is now an immutable binding
}

impl Bar for Foo {
    fn number(&self) -> u32 {
        self.number
    }

    fn inc_oranges(&self) {

        // We can mutate oranges via cell functions even though self is immutable
        let cur_oranges = self.oranges.get();
        self.oranges.set(cur_oranges + 1);

        self.number += 1; // This would be a compile error
    }
}

总之,我们可以通过以下方式有效地实现与您的C#示例等效:

  • 在impls上定义self的不可变绑定
  • 使用单元格类型允许不可变绑定的内部可变性

尽管如此,以这种方式对所有类型进行建模并不是惯用的或高效的。更重要的是知道何时何地允许变异,而不是通过细胞类型微观管理特定领域的变异。