满足Rust借用检查器的结构

时间:2016-09-17 20:44:40

标签: rust borrow-checker

我试图学习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;)。

1 个答案:

答案 0 :(得分:3)

Battlefield::place_ship的签名中,编译器必须假设该函数可以在shipself对象)中存储对Battlefield<'a>的可变引用。那是因为你将ship参数的生命周期与Battlefield的生命周期参数相关联,编译器只查看结构的高级接口,以便所有结构看起来都一样行为相同(否则,将一个字段添加到结构中,即使所有字段都是私有的,也可能是一个重大改变!)。

如果您将ship的声明从ship: &'a mut Ship更改为ship: &mut Ship<'a>,您将看到错误消失(如果该方法的正文对参数不起作用)。但是,如果您尝试在Cell的{​​{1}}字段中存储此指针的副本,这将不再有效,因为现在编译器无法证明ship将活得足够长

你会一直遇到生命周期的问题,因为你想要做的事情不适用于简单的引用。现在,您对ShipBattlefieldCell的定义存在矛盾:您声明Ship持有Battlefield个引用Cell的{​​{1}}它比Ship更长久了。但是,与此同时,您声明Battlefield引用Ship的时间比Cell更长。唯一可行的方法是,如果您在同一Ship语句上声明BattlefieldShip s (因为编译器会将相同的生命周期分配给所有价值观。)

let

您还需要将let (mut ship, mut bf) = (Ship::new(3), Battlefield::new()); 更改为&mut self,以便将&'a mut selfCell分配到self。但是一旦你调用Ship,你就会有效地锁定place_ship,因为编译器会假设Battlefield可能存储一个可变引用(它可以因为它需要对自身进行可变引用作为参数!)。

更好的方法是使用reference counting而不是结合interior mutability的简单引用而不是显式可变性。引用计数意味着您不必处理生命周期(尽管为了避免内存泄漏,您必须使用weak pointers来中断周期)。内部可变性意味着您可以传递不可变引用而不是可变引用;这将避免Battlefield编译器错误,因为根本没有可变借用。