2016-09-17 31 views
1

我在學習Rust,正如你所想象的那樣,借用檢查器是我最大的對手。所以這裏是我的設置,它是遊戲戰艦的一種箱子。遊戲基於Battlefield結構,它由Cell組成。 A Cell可以引用Ship,並且Ship具有所引用的所有Cell的向量,因此它是雙向只讀關係。滿足Rust借用檢查器與結構

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(標準搭建'貨運')。

+1

*但是整個代碼太多了,無法在這裏發佈* - 我**保證**,您可以使代碼小到足以在這裏發佈,同時仍然重現相同的錯誤。參見[MCVE]。 – Shepmaster

+0

可能是http://stackoverflow.com/q/32300132/155423的副本;可能是http://stackoverflow.com/q/20698384/155423或http://stackoverflow.com/q/28833622/155423或http://stackoverflow.com/q/29893978/155423。或者有關可變別名的任何問題。 [就像錯誤說的](https://doc.rust-lang.org/error-index.html#E0499),你不能借用任何**任何可變多次一次。 – Shepmaster

+3

「雙向」 - 「我不認爲存在所有權問題」 - 一旦您擁有雙向關係,總會遇到所有權問題。 –

回答

3

Battlefield::place_ship的簽名開始,編譯器必須假設該函數可能會在selfBattlefield<'a>對象)中存儲對ship的可變引用。這是因爲您將ship參數的生命週期與生命週期參數Battlefield鏈接在一起,編譯器僅查看結構的高級接口,以使看起來相同的所有結構表現相同(否則,添加一個字段添加到結構中,即使所有字段都是私人的,也可能是一個重大改變!)。

如果您將ship的聲明從ship: &'a mut Ship更改爲ship: &mut Ship<'a>,您會看到錯誤消失(如果方法的主體不對該參數執行任何操作)。但是,如果您嘗試在Cellship字段中存儲該指針的副本,則此操作將不再起作用,因爲現在編譯器無法證明Ship的存活時間足夠長。

你會一直遇到與生命期有關的問題,因爲你試圖做的事情不會對簡單的引用起作用。現在,您在Battlefield,CellShip的定義中存在一個矛盾:您聲明Battlefield擁有Cell s,其參考文獻Ship s超過Battlefield。但是,與此同時,您聲明的Ship參考文獻Cell s比Ship長。這將工作的唯一方法是如果您聲明BattlefieldShip s 在相同的let語句(因爲編譯器將分配相同的生命期所有值)。

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

您還需要改變&mut self&'a mut selfCellself分配給Ship。但是,只要您撥打place_ship,您將最終鎖定Battlefield,因爲編譯器會假設Battlefield可能會存儲對其自身的可變引用(可以這樣做,因爲它將自身作爲參數採用可變引用! )。

更好的方法是使用reference counting而不是簡單的參考結合interior mutability而不是明確的可變性。引用計數意味着你不必處理生命週期(儘管這裏你必須打破weak pointers的週期以避免內存泄漏)。內部可變性意味着您可以傳遞不可變引用而不是可變引用;這將避免cannot borrow x as mutable more than once編譯器錯誤,因爲根本沒有可變借入。

+1

IMO,「更好」(但不同結構)的解決方案是不讓子組件具有對父結構的引用,而是隻在需要時通過特定的子方法傳遞父引用。 – Shepmaster

+0

@Shepmaster我想將參考資料存儲在船上的單元格中,以便輕鬆瞭解船舶的所有單元是否已被擊中。但你可以看到我來自垃圾回收背景:) – Leopard2A5

+0

謝謝你的答案弗朗西斯!我不能說我在第一次閱讀時完全理解它:)我發現我仍然有很多東西需要了解鐵鏽。 – Leopard2A5