[英]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: 我的问题是
Battlefield
的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
}
}
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
,编译器必须假设函数可以存储可变引用ship
在self
(在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. 但是,如果您尝试将此指针的副本存储在
Cell
的ship
字段中,那么它将不再起作用,因为现在编译器无法证明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
. 现在,您对
Battlefield
, Cell
和Ship
定义存在矛盾:您要声明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
语句中声明Battlefield
和Ship
(因为编译器将为所有值分配相同的生存期)。
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
才能将Cell
从self
分配到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.