简体   繁体   English

用结构满足Rust借阅检查器

[英]Satisfying the Rust borrow checker with structs

I'm trying to learn Rust, and as you can imagine, the borrow checker is my biggest adversary. 我正在尝试学习Rust,并且您可以想象,借阅检查器是我最大的对手。 So here's my setup, it's a kind of crate for the game battleship. 因此,这是我的设置,它是游戏战舰的板条箱。 The game is based on the Battlefield struct, which consists of Cell s. 该游戏基于由Cell组成的Battlefield结构。 A Cell can reference a Ship and a Ship has a vector of all Cell s it's referenced by, so it's a bi-directional read-only relationship. 一个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>>,
}

My problem is Battlefield 's place_ship function: 我的问题是Battlefieldplace_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
    }
}

It makes sense to me and I don't think there's an ownership problem here, but I'm wrong it seems: 这对我来说很有意义,我认为这里不存在所有权问题,但看来我是错的:

#[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));
                                                                 ^~~~

I know this is just a short excerpt, but the whole code is too much to post here. 我知道这只是一个简短的摘录,但是整个代码太多了,无法在此处发布。 The project can be found here (standard build with 'cargo build'). 可以在此处找到项目 (带有“货物构建”的标准构建)。

From the signature of Battlefield::place_ship , the compiler must suppose that the function may store a mutable reference to ship in self (the Battlefield<'a> object). 从签名Battlefield::place_ship ,编译器必须假设函数可以存储可变引用shipself (在Battlefield<'a>对象)。 That's because you're linking the lifetime of the ship parameter with the lifetime parameter of Battlefield , and the compiler only looks at the high-level interface of a struct, so that all structs that look the same behave the same (otherwise, adding a field to a struct, even if all fields are private, might be a breaking change!). 那是因为您要将ship参数的生存期与Battlefield的lifetime参数链接起来,并且编译器仅查看结构的高级接口,因此所有看起来相同的结构的行为都相同(否则,添加一个字段,即使所有字段都是私有的,也可能是一个重大更改!)。

If you change the declaration of ship from ship: &'a mut Ship to ship: &mut Ship<'a> , you'll see that the error goes away (if the method's body does nothing with the parameter). 如果将ship声明从ship: &'a mut Ship更改为ship: &mut Ship<'a> ,则会看到错误消失了(如果方法的主体对参数不执行任何操作)。 However, if you try to store a copy of this pointer in Cell 's ship field, this will no longer work, as now the compiler cannot prove that the Ship will live long enough. 但是,如果您尝试将此指针的副本存储在Cellship字段中,那么它将不再起作用,因为现在编译器无法证明Ship寿命足够长。

You'll keep running into issues with lifetimes, because what you're trying to do will not work with simple references. 您将不断遇到生命周期问题,因为您尝试执行的操作不适用于简单的引用。 Right now, there's a contradiction in your definitions of Battlefield , Cell and Ship : you're declaring that Battlefield holds Cell s who references Ship s that outlive the Battlefield . 现在,您对BattlefieldCellShip定义存在矛盾:您要声明Battlefield拥有Cell ,而Cell引用的是Shipship,而Ship超过了Battlefield However, at the same time, you're declaring that Ship references Cell s that outlive the Ship . 但是,与此同时,您声明Ship引用了Ship寿命更长的Cell The only way this will work is if you declare the Battlefield and the Ship s on the same let statement (as the compiler will assign the same lifetime to all values). 唯一可行的方法是在同一个let语句中声明BattlefieldShip (因为编译器将为所有值分配相同的生存期)。

let (mut ship, mut bf) = (Ship::new(3), Battlefield::new());

You'll also need to change &mut self to &'a mut self to assign a Cell from self to a Ship . 您还需要将&mut self更改为&'a mut self才能将Cellself分配到Ship But then as soon as you call place_ship , you'll effectively end up locking the Battlefield , as the compiler will suppose that the Battlefield may store a mutable reference to itself (which it can because it takes a mutable reference to itself as a parameter!). 但是,一旦您调用place_ship ,您将最终有效地锁定了Battlefield ,因为编译器会假设Battlefield可能存储对自身的可变引用(之所以可以这样做是因为它将对自身的可变引用作为参数! )。

A better approach would be to use reference counting instead of simple references combined with interior mutability instead of explicit mutability. 更好的方法是使用引用计数,而不是将简单引用与内部可变性相结合,而不是显式可变性。 Reference counting means that you won't have to deal with lifetimes (though here's you'd have to break cycles with weak pointers in order to avoid memory leaks). 引用计数意味着您不必处理生命周期(尽管这里您必须使用弱指针来中断周期,以避免内存泄漏)。 Interior mutability means that you can pass immutable references instead of mutable references; 内部可变性意味着您可以传递不可变的引用,而不是可变的引用。 this will avoid cannot borrow x as mutable more than once compiler errors since there will be no mutable borrows at all. 这将避免cannot borrow x as mutable more than once编译器错误,因为根本没有可变借用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM