特征的不可变数据
Immutable data on a trait
我正在尝试使用返回一些数据的方法来定义特征,但是 impl
不能更改此数据,即它只能设置一次并在整个生命周期内保持该值的 impl
。有什么办法可以确保这一点?
以下是我在 C# 中的实现方式,供参考:
public abstract class Foo
{
private readonly uint number;
public Foo(uint number) { this.number = numbers; }
public uint GetNumber() { return number; }
}
您问题的简短回答是否。无法以类似于 C# 方法的方式完成此操作。幸运的是,Rust 提供了比 C# 更好的可变性控制。
了解不变性在 Rust 中的工作原理以及它与 C# 和 Java 等语言的区别很重要。
C# 中的不变性
class Foo {
readonly Bar bar = new Bar();
uint lives;
}
一些注意事项:
- 不变性已定义 per-field。
- 不可变性很浅。例如,即使对
bar
的引用是不可变的,但 bar
引用的值仍然是可变的。
- C# 中的不变性很容易被反射破坏。有edge cases处不用反射也能颠覆
Rust 中的不变性
struct Foo {
bar: Bar,
lives: u32
}
首先要注意的是结构定义没有说明其字段的不变性。那是因为 Rust 中没有 field-level 可变性这样的东西。 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 mut foo = 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 中,如果特征声明self
的可变绑定,self
的任何部分都可以改变。幸运的是,有办法解决这个问题。
*内部可变性
Rust 提供了一种通过 cell 模块改变作为不可变绑定一部分的值的方法。长话短说,这些类型允许突变,同时仍然提供使用不可变绑定提供的保证。它通过将 compile-time (zero-cost) 检查移动到运行时检查来做到这一点。
现在让我们把它们放在一起:
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
的不可变绑定
- 使用单元类型允许不可变绑定的内部可变性
综上所述,以这种方式为所有类型建模既不符合习惯也不符合性能。更重要的是知道何时何地允许突变是合适的,而不是 micro-managing 通过细胞类型对特定字段进行突变。
我正在尝试使用返回一些数据的方法来定义特征,但是 impl
不能更改此数据,即它只能设置一次并在整个生命周期内保持该值的 impl
。有什么办法可以确保这一点?
以下是我在 C# 中的实现方式,供参考:
public abstract class Foo
{
private readonly uint number;
public Foo(uint number) { this.number = numbers; }
public uint GetNumber() { return number; }
}
您问题的简短回答是否。无法以类似于 C# 方法的方式完成此操作。幸运的是,Rust 提供了比 C# 更好的可变性控制。
了解不变性在 Rust 中的工作原理以及它与 C# 和 Java 等语言的区别很重要。
C# 中的不变性
class Foo {
readonly Bar bar = new Bar();
uint lives;
}
一些注意事项:
- 不变性已定义 per-field。
- 不可变性很浅。例如,即使对
bar
的引用是不可变的,但bar
引用的值仍然是可变的。 - C# 中的不变性很容易被反射破坏。有edge cases处不用反射也能颠覆
Rust 中的不变性
struct Foo {
bar: Bar,
lives: u32
}
首先要注意的是结构定义没有说明其字段的不变性。那是因为 Rust 中没有 field-level 可变性这样的东西。 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 mut foo = 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 中,如果特征声明self
的可变绑定,self
的任何部分都可以改变。幸运的是,有办法解决这个问题。
*内部可变性
Rust 提供了一种通过 cell 模块改变作为不可变绑定一部分的值的方法。长话短说,这些类型允许突变,同时仍然提供使用不可变绑定提供的保证。它通过将 compile-time (zero-cost) 检查移动到运行时检查来做到这一点。
现在让我们把它们放在一起:
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
的不可变绑定
综上所述,以这种方式为所有类型建模既不符合习惯也不符合性能。更重要的是知道何时何地允许突变是合适的,而不是 micro-managing 通过细胞类型对特定字段进行突变。