【问题标题】:Satisfying the Rust borrow checker with structs使用结构满足 Rust 借用检查器
【发布时间】:2016-09-17 20:44:40
【问题描述】:

我正在尝试学习 Rust,正如你所想象的那样,借用检查器是我最大的对手。所以这是我的设置,它是游戏战舰的一种板条箱。该游戏基于Battlefield 结构,该结构由Cells 组成。一个Cell 可以引用一个Ship,而一个Ship 具有一个包含所有引用它的Cells 的向量,因此它是一个双向只读关系。

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

我的问题是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
    }
}

这对我来说很有意义,我认为这里没有所有权问题,但似乎我错了:

#[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(带有“货物构建”的标准构建)。

【问题讨论】:

  • 但整个代码太多了,无法在此处发布 — 我保证您可以使代码足够小,以便在此处发布,同时仍能重现同样的错误。见minimal reproducible example
  • 可能是stackoverflow.com/q/32300132/155423的副本;也许stackoverflow.com/q/20698384/155423stackoverflow.com/q/28833622/155423stackoverflow.com/q/29893978/155423。或任何关于可变别名的问题。 Like the error says,您不能一次多次借用 anything 作为 mutable。
  • “双向” - “我不认为存在所有权问题” - 一旦建立了双向关系,所有权问题就会出现。
  • @SebastianRedl 为什么感谢您的建设性评论。幸好你记得在你的报价中保留“只读”......!在 rust 方面我可能是个菜鸟,但非 mut ref 与所有权无关。
  • @Leopard2A5 引用,mut 或 non-mut,以完全相同的方式影响所有权:所有者必须保持对象处于活动状态,直到引用消失。 mut 或 non-mut 只会影响可能采用的其他引用。是的,由于别名问题,mut 引用使双向关系更加困难,但 Rust 使任何类型的双向关系变得困难。看看实现一个双向链表是多么棘手。底线是,在 Rust 中,重新设计通常是最好的选择,直到双向关系消失。

标签: rust borrow-checker


【解决方案1】:

根据Battlefield::place_ship 的签名,编译器必须假设该函数可以在selfBattlefield&lt;'a&gt; 对象)中存储对ship 的可变引用。这是因为您将ship 参数的生命周期与Battlefield 的生命周期参数链接起来,并且编译器只查看结构的高级接口,因此所有看起来相同的结构的行为都相同(否则,将字段添加到结构中,即使所有字段都是私有的,也可能是一项重大更改!)。

如果您将ship 的声明从ship: &amp;'a mut Ship 更改为ship: &amp;mut Ship&lt;'a&gt;,您将看到错误消失(如果方法的主体对参数没有任何作用)。但是,如果您尝试将此指针的副本存储在Cellship 字段中,这将不再有效,因为现在编译器无法证明Ship 的寿命足够长。

您将不断遇到生命周期问题,因为您尝试执行的操作不适用于简单的引用。现在,您对BattlefieldCellShip 的定义存在矛盾:您声明Battlefield 拥有引用Ships 的Cells,它比Battlefield 更长寿。但是,与此同时,您声明Ship 引用了比Ship 寿命更长的Cells。这将起作用的唯一方法是,如果您在同一 let 语句中声明 BattlefieldShips (因为编译器将为所有值分配相同的生命周期)。 p>

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

您还需要将&amp;mut self 更改为&amp;'a mut self 以将Cellself 分配给Ship。但是一旦你调用place_ship,你最终会有效地锁定Battlefield,因为编译器会假设Battlefield可能会存储一个对自身的可变引用(它可以,因为它需要一个可变引用将自身作为参数!)。

更好的方法是使用reference counting 而不是简单的引用结合interior mutability 而不是显式可变性。引用计数意味着您不必处理生命周期(尽管为了避免内存泄漏,您必须使用 weak pointers 打破循环)。内部可变性意味着您可以传递不可变引用而不是可变引用;这将避免cannot borrow x as mutable more than once 编译器错误,因为根本不会有可变借用。

【讨论】:

  • IMO,“更好”(但结构不同)的解决方案是不让子组件具有对父结构的引用,而是仅在特定子方法需要时传递父引用。
  • @Shepmaster 我想为船上的单元存储一个参考,以便轻松找出一艘船的所有单元是否都被击中。但是你可以看到我来自垃圾收集的背景:)
  • 感谢弗朗西斯的回答!我不能说我在第一次阅读时就完全理解了 :) 我看到我还有很多关于 rust 的知识。
  • @Leopard2A5 如果你有一些方法impl Ship { fn all_were_hit(&amp;self) -&gt; bool {} },请尝试将其更改为impl Ship { fn all_were_hit(&amp;self, &amp;Battlefield) -&gt; bool {} }(或任何合适的类型),看看效果如何。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-12
  • 1970-01-01
  • 1970-01-01
  • 2022-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多