我试图学习Rust,而且你可以想象,借阅检查器是我最大的对手。所以这就是我的设置,它是游戏战舰的一种箱子。该游戏基于Battlefield
结构,由Cell
组成。 Cell
可以引用Ship
,而Ship
可以引用其所引用的所有Cell
的向量,因此它是双向阅读 - 只有关系。
pub struct Battlefield<'a> {
cells: Vec<Vec<Cell<'a>>>,
}
#[derive(Debug, PartialEq)]
pub struct Cell<'a> {
ship: Option<&'a Ship<'a>>
}
#[derive(Debug, PartialEq)]
pub struct Ship<'a> {
length: usize,
cells: Vec<&'a Cell<'a>>,
}
我的问题是Battlefield
&#39; place_ship
功能:
impl<'a> Battlefield<'a> {
pub fn place_ship(&mut self,
ship: &'a mut Ship,
x: usize,
y: usize,
orientation: Orientation)
-> PlaceResult {
// check ship placement in bounds
// check affected cells are free
// set cells' ship ref to ship
// add cell refs to ship's cells field
}
}
这对我有意义,我不认为这里存在所有权问题,但我似乎错了:
#[cfg(test)]
mod tests {
use super::{Battlefield, X, Y};
use super::Orientation::*;
use super::super::ship::Ship;
#[test]
fn assert_ship_placement_only_in_bounds() {
let mut ship = Ship::new(3);
let mut bf = Battlefield::new();
assert_eq!(Ok(()), bf.place_ship(&mut ship, 0, 0, Horizontal));
assert_eq!(Ok(()), bf.place_ship(&mut ship, 5, 5, Vertical));
}
}
src/battlefield.rs:166:47: 166:51 error: cannot borrow `ship` as mutable more than once at a time [E0499]
src/battlefield.rs:166 assert_eq!(Ok(()), bf.place_ship(&mut ship, 5, 5, Vertical));
^~~~
src/battlefield.rs:165:47: 165:51 note: first mutable borrow occurs here
src/battlefield.rs:165 assert_eq!(Ok(()), bf.place_ship(&mut ship, 0, 0, Horizontal));
^~~~
我知道这只是一个简短的摘录,但整个代码太多了,无法在此发布。 project can be found here(使用&#39;货物构建的标准版本&#39;)。
答案 0 :(得分:3)
从Battlefield::place_ship
的签名中,编译器必须假设该函数可以在ship
(self
对象)中存储对Battlefield<'a>
的可变引用。那是因为你将ship
参数的生命周期与Battlefield
的生命周期参数相关联,编译器只查看结构的高级接口,以便所有结构看起来都一样行为相同(否则,将一个字段添加到结构中,即使所有字段都是私有的,也可能是一个重大改变!)。
如果您将ship
的声明从ship: &'a mut Ship
更改为ship: &mut Ship<'a>
,您将看到错误消失(如果该方法的正文对参数不起作用)。但是,如果您尝试在Cell
的{{1}}字段中存储此指针的副本,这将不再有效,因为现在编译器无法证明ship
将活得足够长
你会一直遇到生命周期的问题,因为你想要做的事情不适用于简单的引用。现在,您对Ship
,Battlefield
和Cell
的定义存在矛盾:您声明Ship
持有Battlefield
个引用Cell
的{{1}}它比Ship
更长久了。但是,与此同时,您声明Battlefield
引用Ship
的时间比Cell
更长。唯一可行的方法是,如果您在同一Ship
语句上声明Battlefield
和Ship
s (因为编译器会将相同的生命周期分配给所有价值观。)
let
您还需要将let (mut ship, mut bf) = (Ship::new(3), Battlefield::new());
更改为&mut self
,以便将&'a mut self
从Cell
分配到self
。但是一旦你调用Ship
,你就会有效地锁定place_ship
,因为编译器会假设Battlefield
可能存储一个可变引用(它可以因为它需要对自身进行可变引用作为参数!)。
更好的方法是使用reference counting而不是结合interior mutability的简单引用而不是显式可变性。引用计数意味着您不必处理生命周期(尽管为了避免内存泄漏,您必须使用weak pointers来中断周期)。内部可变性意味着您可以传递不可变引用而不是可变引用;这将避免Battlefield
编译器错误,因为根本没有可变借用。