我在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
    }
}

然后我修改了它,以返回一个引用:

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
    }
}

我这样做是为了将self的生存期和返回值链接在一起,以便如果板的状态更改,则不能再使用对Status的引用.

然后我try 了这个代码:

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
}

这会导致编译器错误,说明它无法返回对所拥有数据的引用.为什么第一个代码片段没有这个问题?

推荐答案

我的猜测是,第一个代码段的值为lifetime extension,而第二个代码段是对显式局部变量的引用,因此已经有了显式的生存期.生命周期延长的规则很复杂,所以我会让其他人来讨论这方面的细节.相反,我想摆出一个略有不同的设计.

你的所作所为令人钦佩.事实上,这太棒了.我从来没有想过用引用将这样的状态变量绑定到数据 struct .这就是说,你在向Rust A bit撒谎,我们可以更直接地获得你想要的行为.

基本上,你的分数是Status.这是一种价值,它不是借来的,假装是借来的有点尴尬.这不是错的,只是很尴尬.但你希望它semantically表现得像是借来的,即使它不是借来的.我们可以使用PhantomData来做到这一点.

考虑一下这一点.别管你的Status枚枚举号.把它改成CopyClone,因为它既好又简单,而且永远不要引用它.它仍然可以明智地用作独立的数据 struct ,只要您永远不会认为它绑定到任何特定的板上.

现在,当你want要把它绑定到一个特定的板上时,使用一个新的 struct ,我称之为BoardStatus.

struct BoardStatus<'a> {
  status: Status,
  _phantom: PhantomData<&'a Status>,
}

BoardStatus分实际上只是Status分.它唯一的非零大小的字段是Status,因此两者的表示应该是相同的.但它也有PhantomData分.PhantomData<T>是一个零大小的类型(这意味着它在运行时不会占用空间),pretends在生命周期内包含T.因此,BoardStatus是将在持续时间'a内被borrow 的pretendsStatus(拥有的状态值,不借入).那么您的status方法就可以具有此返回类型.

fn<'a> status(&'a self) -> BoardStatus<'a>

或者,用lifetime elision

fn status(&self) -> BoardStatus<'_>

这样,就有了Status枚举和BoardStatus struct 之间的区别,前者是可独立测试的,没有额外的负担;后者still只是一个状态,但显式地绑定到一个板.

最好的部分是:这一切都有zero的开销.在运行时没有实际的指针,所以BoardStatus在运行时与Status的效率完全一样,没有间接性.一个真正零成本的抽象.

Rust相关问答推荐

为什么我需要在这个代码示例中使用&

Rust中的相互递归特性与默认实现

如何找到一个数字在二维数组中的位置(S)?

铁 rust 中的共享对象实现特征

在Rust中是否可以使用Rc自动化约束传播

将PathBuf转换为字符串

`Pin`有没有不涉及不安全代码的目的?

正在将带有盒的异步特征迁移到新的异步_fn_in_特征功能

由于生存期原因,返回引用的闭包未编译

当我编译 Rust 代码时,我是否缺少 AVX512 的目标功能?

`UnsafeCell` 在没有锁定的情况下跨线程共享 - 这可能会导致 UB,对吗?

Rust 并行获取对 ndarray 的每个元素的可变引用

在描述棋盘时如何最好地使用特征与枚举

Rust中的标记特征是什么?

str 和 String 的 Rust 生命周期

以 `static` 为前缀的闭包是什么意思?我什么时候使用它?

为什么指定生命周期让我返回一个引用?

是否有适当的方法在参考 1D 中转换 2D 数组

类型组的通用枚举

在使用大型表达式时(8k 行需要一小时编译),是否可以避免 Rust 中的二次编译时间?