返回 Rust 中枚举变体的引用是个好主意吗?

Blu*_*ing 5 rust

我正在 Rust 中制作一个 Tic-Tac-Toe 游戏作为初学者项目,当我遇到这个问题时,我正在计算游戏的状态(获胜、平局等)。我首先写了下面的代码:

fn status(&self) -> Status {
    if Board::has_won(self.x_board) {
        Status::Won(true)
    } else if Board::has_won(self.o_board) {
        Status::Won(false)
    } else if self.is_full() {
        Status::Draw
    } else {
        Status::None
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我修改它以返回引用:

fn status(&self) -> &Status {
    if Board::has_won(self.x_board) {
        &Status::Won(true)
    } else if Board::has_won(self.o_board) {
        &Status::Won(false)
    } else if self.is_full() {
        &Status::Draw
    } else {
        &Status::None
    }
}
Run Code Online (Sandbox Code Playgroud)

我这样做是为了将 self 的生命周期和返回值链接在一起,因此如果板的状态发生更改,则无法再使用对状态的引用。

然后我尝试了这段代码:

fn status(&self) -> &Status {
    let status;
    if Board::has_won(self.x_board) {
        status = Status::Won(true)
    } else if Board::has_won(self.o_board) {
        status = Status::Won(false)
    } else if self.is_full() {
        status = Status::Draw
    } else {
        status = Status::None
    }

    &status
}
Run Code Online (Sandbox Code Playgroud)

它导致编译器错误,指出它无法返回对拥有数据的引用。为什么第一个片段没有这个问题?

Sil*_*olo 4

我的猜测是,第一个片段受到生命周期扩展的影响,而第二个片段是对显式局部变量的引用,因此已经具有显式生命周期。延长生命周期的规则很复杂,因此我将让其他人讨论其细节。相反,我想提出一个稍微不同的设计。

你所做的事情令人钦佩。事实上,这太棒了。我从未想过将这样的状态变量与带有引用的数据结构联系起来。话虽这么说,你在这里对 Rust 撒了一点,我们可以更直接地得到你想要的行为。

基本上,你有一个Status. 它是一个值,它不是借来的,假装它是借来的只是有点尴尬。这并没有错,只是尴尬。但您希望它在语义上表现得像借来的,即使事实并非如此。我们可以使用 aPhantomData来做到这一点。

考虑一下这一点。不要Status管你的枚举。制作它CopyClone,因为它又好又简单,并且永远不要引用它。它仍然可以作为独立的数据结构合理地使用,只要您永远不会假设它与任何特定的板相关联。

现在,当您将其绑定到特定的板时,请使用一个新的结构,我将其称为BoardStatus.

struct BoardStatus<'a> {
  status: Status,
  _phantom: PhantomData<&'a Status>,
}
Run Code Online (Sandbox Code Playgroud)

ABoardStatus确实只是一个Status。它唯一的非零大小字段是 a Status,因此两者的表示形式应该相同。但它也有一个PhantomData. PhantomData<T>是一个零大小的类型(意味着它在运行时不占用空间),假装包含用于T生命周期的目的。假装借用持续时间 的BoardStatusa Status(拥有的状态值,无借用)也是如此。那么你的方法就可以有这个返回类型。'astatus

fn<'a> status(&'a self) -> BoardStatus<'a>
Run Code Online (Sandbox Code Playgroud)

或者,终身省略

fn status(&self) -> BoardStatus<'_>
Run Code Online (Sandbox Code Playgroud)

这样,Status枚举之间就有了区别,它是可独立测试的并且没有额外的包袱;以及BoardStatus结构,它仍然只是一种状态,但明确与董事会相关联。

最好的部分是:这一切都是开销。运行时没有实际的指针,因此BoardStatus与运行时的效率完全相同Status,没有间接寻址。真正的零成本抽象。

  • 我认为第一个参考示例有效,不是因为_生命周期延长_,而是因为_[右值静态提升](https://github.com/rust-lang/rfcs/blob/master/text/1414-rvalue_static_promotion.md )_,现象类似但不完全相同。 (2认同)