2016-09-17 1 views
1

J'essaie d'apprendre à rouiller, et comme vous pouvez l'imaginer, le vérificateur d'emprunt est mon plus grand adversaire. Donc, voici ma configuration, c'est une sorte de caisse pour le cuirassé de jeu. Le jeu est basé sur la structure Battlefield, qui se compose de Cell s. Un Cell peut faire référence à une Ship et un Ship a un vecteur de tous Cell s il est référencé par, il est donc une lecture seule relation bidirectionnelle.Satisfaire le contrôleur d'emprunt Rust avec des structures

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

Mon problème est fonction de place_shipBattlefield:

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

Il est logique pour moi et je ne pense pas qu'il y ait un problème de propriété ici, mais je me trompe, il semble:

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

Je sais que c'est juste un court extrait, mais tout le code est trop pour poster ici. Le project can be found here (build standard avec 'cargo build').

+1

* mais l'ensemble du code est trop important pour publier ici * - je ** garantis ** que vous pouvez rendre le code suffisamment petit pour être publié ici tout en reproduisant la même erreur. Voir [MCVE]. – Shepmaster

+0

Probablement un doublon de http://stackoverflow.com/q/32300132/155423; peut-être http://stackoverflow.com/q/20698384/155423 ou http://stackoverflow.com/q/28833622/155423 ou http://stackoverflow.com/q/29893978/155423. Ou toute question sur l'aliasing mutable. [Comme l'erreur indique] (https://doc.rust-lang.org/error-index.html#E0499), vous ne pouvez pas emprunter ** n'importe quoi ** comme mutable plus d'une fois à la fois. – Shepmaster

+3

"bidirectionnel" - "Je ne pense pas qu'il y ait un problème de propriété" - Il y a toujours un problème de propriété une fois que vous avez des relations bidirectionnelles. –

Répondre

3

De la signature de Battlefield::place_ship, le compilateur doit supposer que la fonction peut stocker une référence mutable à ship dans self (l'objet Battlefield<'a>). C'est parce que vous liez la durée de vie du paramètre ship avec le paramètre de durée de vie Battlefield, et le compilateur regarde seulement à l'interface de haut niveau d'une struct, de sorte que tous les struct qui ont le même aspect se comportent de la même (sinon, l'ajout d'un champ à une structure, même si tous les champs sont privés, pourrait être un changement de rupture!).

Si vous modifiez la déclaration de ship de ship: &'a mut Ship à ship: &mut Ship<'a>, vous verrez que l'erreur disparaît (si le corps de la méthode ne fait rien avec le paramètre). Cependant, si vous essayez de stocker une copie de ce pointeur dans le champ ship, cela ne fonctionnera plus, car maintenant le compilateur ne peut pas prouver que le Ship va vivre assez longtemps.

Vous allez continuer à courir dans des problèmes avec des durées de vie, parce que ce que vous essayez de faire ne fonctionnera pas avec des références simples. En ce moment, il y a une contradiction dans vos définitions de Battlefield, Cell et Ship: vous déclarez que Battlefield détient Cell s qui référence Ship s qui survivent à Battlefield. Cependant, en même temps, vous déclarez que Ship références Cell s qui survivent le Ship. La seule façon dont cela fonctionnera est si vous déclarez le Battlefield et le Ship s sur la même instruction let (car le compilateur assignera la même durée de vie à toutes les valeurs).

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

Vous aurez également besoin de changer &mut self-&'a mut self pour attribuer un Cell de self à un Ship. Mais dès que vous appelez place_ship, vous finirez par bloquer efficacement le Battlefield, comme le compilateur supposera que le Battlefield peut stocker une référence mutable à lui-même (ce qui peut, car il faut une référence mutable à lui-même en tant que paramètre!).

Une meilleure approche serait d'utiliser reference counting au lieu de références simples combinées avec interior mutability au lieu de mutabilité explicite. Le comptage des références signifie que vous n'aurez pas à gérer les durées de vie (bien que vous deviez rompre les cycles avec weak pointers afin d'éviter les fuites de mémoire). La mutabilité intérieure signifie que vous pouvez passer des références immuables au lieu de références mutables; cela évitera les erreurs du compilateur cannot borrow x as mutable more than once car il n'y aura pas d'emprunts mutables du tout.

+1

IMO, la solution «meilleure» (mais différemment structurée) consiste à ne pas laisser les composants enfants avoir des références aux structures parentes, mais plutôt à ne transmettre la référence parente que lorsque nécessaire par une méthode enfant spécifique. – Shepmaster

+0

@Shepmaster Je veux stocker une référence aux cellules du vaisseau afin de savoir facilement si toutes les cellules d'un vaisseau ont été touchées. Mais vous pouvez voir que je viens d'un fond garni de déchets :) – Leopard2A5

+0

Merci pour la réponse Francis! Je ne peux pas dire que je l'ai bien compris à la première lecture :) Je vois que j'ai encore beaucoup à apprendre sur la rouille. – Leopard2A5