如何解决Rust 生命周期混淆
我目前正在自学 Rust,并通过实施井字游戏进行练习。
我有一个 Board 结构(Cell
和 GameState
是简单的枚举,SIZE
是 3 usize
):
struct Board {
state : GameState,cells : [[Cell; SIZE]; SIZE],}
impl Board {
fn mark(&mut self,pos: &Position,player: Player) {
// varIoUs checks omitted
// mark cell for player
self.cells[pos.row][pos.col] = Cell::Filled(player);
// check whether player has won either via a full row or column (diagonals omitted):
if (0..SIZE).map(|i| &self.cells[pos.row][i]).all(|c| *c == Cell::Filled(player)) ||
(0..SIZE).map(|i| &self.cells[i][pos.col]).all(|c| *c == Cell::Filled(player)) {
self.state = GameState::Winner(player);
}
}
到目前为止一切都很好……但是代码重复很丑。
所以我的下一步是引入一个闭包并用它替换 if 中的重复:
let all_in_line = |cell_access| (0..SIZE).map(cell_access).all(|c : &Cell| *c == Cell::Filled(player));
if all_in_line(|i : usize| &self.cells[pos.row][i]) ||
all_in_line(|i : usize| &self.cells[i][pos.col]) {
虽然这不起作用,因为它需要 all_in_line
是通用的(因为我传递给它的两个闭包具有不同的匿名类型),所以我的下一步是做一些类似于 type 的事情擦除:
fn mark(&mut self,player: Player) {
let all_in_line = |cell_access : Box<dyn Fn(usize) -> &Cell>| (0..SIZE).map(cell_access).all(|c : &Cell| *c == Cell::Filled(player));
if all_in_line(Box::new(|i : usize| &self.cells[pos.row][i])) ||
all_in_line(Box::new(|i : usize| &self.cells[i][pos.col])) {
self.state = GameState::Winner(player);
}
但是现在 rust 编译器抱怨 cell_access
的类型说明符中返回的引用缺少生命周期参数:
error[E0106]: missing lifetime specifier
let all_in_line = |cell_access : Box<dyn Fn(usize) -> &Cell>| (0..SIZE).map(cell_access).all(|c : &Cell| *c == Cell::Filled(player));
^ expected named lifetime parameter
我尝试将 mark
修改为 fn mark<'a>(&'a mut self,/*...*/)
并相应地更新 cell_access
的类型:Box<dyn Fn(usize) -> &'a Cell>
但这失败了:
error[E0597]: `self` does not live long enough
fn mark<'a>(&'a mut self,player: Player) {
-- lifetime `'a` defined here
...
if all_in_line(Box::new(|i : usize| &self.cells[pos.row][i]))
----------- -^^^^------------------
| ||
| |borrowed value does not live long enough
| returning this value requires that `self` is borrowed for `'a`
value captured here
...
}
- `self` dropped here while still borrowed
此时,我已经想不通出了什么问题以及如何解决。
解决方法
但是现在 rust 编译器抱怨 cell_access 的类型说明符中缺少返回引用的生命周期参数:
据我所知,这里的问题是,为了能够以这种形式编写代码,cell_access
函数的签名需要引用其有效的生命周期 /em>,这是不可能的,因为那个生命周期没有名字——它是闭包创建中隐含的 self
的本地重新借用。您对 &'a mut self
的尝试不起作用,因为它没有捕捉到闭包的 self
是函数 self
的重新借用的事实,因此不需要完全存活相同的生命周期,因为可变借用的生命周期是不变的而不是协变的;不能随意将它们视为较短(因为这会破坏可变引用的排他性)。
最后一点让我知道如何解决这个问题:将 all_in_line
代码移动到一个函数中,该函数通过 immutable 引用接受 self
。这编译:
impl Board {
fn mark<'a>(&'a mut self,pos: &Position,player: Player) {
if self.all_in_line_either_direction(pos,player)
{
// self.state = GameState::Winner(player);
}
}
fn all_in_line_either_direction<'a>(&'a self,player: Player) -> bool {
let all_in_line = |cell_access: Box<dyn Fn(usize) -> &'a Cell>| {
(0..SIZE)
.map(cell_access)
.all(|c: &Cell| *c == Cell::Filled(player))
};
all_in_line(Box::new(|i: usize| &self.cells[pos.row][i]))
|| all_in_line(Box::new(|i: usize| &self.cells[i][pos.col]))
}
}
然而,虽然上述方法确实有效,但我认为这里最好的选择是,与其尝试使 all_in_line
成为闭包,不如使其成为一个单独的函数(可以是通用的),并避免使用 { {1}}。
在这个位置,我们可以写出我们实际需要的生命周期——函数的生命周期泛型 Box<dyn Fn>
意味着生命周期 <'a>
持续 只要 'a
需要它(生命周期参数不能在函数调用的中间结束),而采用盒装函数特征的闭包没有自己的泛型作用域。
all_in_line
然后您必须将 fn all_in_line<'a,F>(player: Player,cell_access: F) -> bool
where
F: Fn(usize) -> &'a Cell
{
(0..SIZE).map(cell_access).all(|c| *c == Cell::Filled(player))
}
传递给每个调用,但没有其他复杂情况。
如果您愿意,您甚至可以在 player
函数内部编写 all_in_line
的定义,使其不存在于外部。这是否是更具可读性的代码取决于您的决定。
我添加了一些片段来检查代码是否已编译;希望这一切都与您实际所做的相符:
mark
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。