2016-11-29 15 views
7

Mam problem z nadawaniem struct zmiennej składowej, która jest referencją do innego typu. Oto moja struktura i realizacja:.Odwołanie do elementu w wektorze

struct Player<'a> { 
    current_cell: &'a Cell, 
} 

impl<'a> Player<'a> { 
    pub fn new(starting_cell: &'a Cell) -> Player<'a> { 
     Player { current_cell: starting_cell } 
    } 
} 

Zawodnik ma odniesienie do aktualnej Cell że są tu jest mój Game struct i jego realizacja:

struct Game { 
    is_running: bool, 
    cells: Vec<Cell>, 
} 

impl Game { 
    pub fn new() -> Game { 
     let cells = construct_cells(); 
     let player = Player::new(cells[0]); 

     Game { 
      is_running: false, 
      cells: cells, 
     } 
    } 
} 

cells jest wektorem Cell s. Kiedy tworzę grę, tworzę wektor komórek w construct_cells(), a następnie uruchomię odtwarzacz w pierwszej komórce. Błąd otrzymuję to:

expected &Cell, found struct `Cell` 

widzę, że nie jestem przekazując odwołanie kiedy utworzyć Player, ale jeśli mogę zmienić parametr &cells[0] to krzyczy na mnie za wypożyczenie całego wektora i następnie próbuję użyć go ponownie podczas tworzenia struktury Game. Więc co się dzieje? Jak mogę podać odtwarzaczowi odniesienie do Cell?

+0

Czy dobrze rozumiesz, jakie są referencje (lub wskaźniki), a konkretnie: * zwisające odwołanie * (lub wskaźnik)? Co by się stało, gdybyś dała przyjacielowi kartkę z adresem na niej (123 Main St.), a następnie ** przeniosłeś **?Jak czuliby się nowi właściciele domu, gdyby twój przyjaciel po prostu się pojawił i zaczął rozmawiać z ludźmi w domu, w którym mieszkałeś, jakbyś był tobą? – Shepmaster

+0

@Shepmaster Wiem, czym jest zwisający wskaźnik. Chyba po prostu nie zdawałem sobie sprawy, że to rozwiązanie może na to pozwolić. Jak zatem uzyskać ten sam wynik? Czy to, co próbuję zrobić, jest możliwe? – Dooskington

+1

* Po prostu nie zdawałem sobie sprawy, że to rozwiązanie może pozwolić na jedno * - i dlatego Rust jest cudownym językiem, IMO. Kiedy przenosisz "komórki" z zmiennej lokalnej do struktury "Game", unieważniłeś wszelkie odniesienia do wektora, takie jak te, które dałeś "graczowi". Języki takie jak C lub C++ pozwolą ci na to, AFAIK, i pozwolą na awarię kodu w czasie wykonywania. Trudno powiedzieć, co naprawdę chcesz zrobić, ponieważ 'player' nigdy nie jest używany. – Shepmaster

Odpowiedz

10

Wbrew pozorom przechowywanie odniesienia do obiektu przechowywanego w zmiennym wektorze jest bezpieczne. Pamiętaj, że wektory mogą rosnąć; gdy wektor przekroczy swoją pojemność, rośnie on przez przydzielenie większej tablicy i przeniesienie wszystkich obiektów do nowej lokalizacji. Wszelkie odniesienia do obiektów w wektorze pozostaną zwisające, a Rust nie dopuści do tego. (Również wektor może zostać skrócony lub wyczyszczony, w takim przypadku wszelkie odwołania do jego elementów będą oczywiście wskazywały na pamięć zwalnianą.) Ten sam problem występowałby w przypadku C++ std::vector.

Istnieje kilka sposobów obejścia tego problemu. Jednym z nich jest przejście z bezpośrednim odniesieniem do Cell do bezpiecznego back-odsyłającego wynika, że ​​składa się z tylnej wskaźnik do Game i wskaźnik do elementu wektora:

struct Player<'a> { 
    game: &'a Game, 
    cell_idx: usize, 
} 

impl<'a> Player<'a> { 
    pub fn new(game: &'a Game, cell_idx: usize) -> Player<'a> { 
     Player { 
      game: game, 
      cell_idx: cell_idx, 
     } 
    } 
    pub fn current_cell_name(&self) -> &str { 
     &self.game.cells[self.cell_idx].name 
    } 
} 

Pełną compilable przykładem jest at the playground.

Powyższe może wyglądać jak oszustwo - w końcu Rust jest językiem systemowym, który ma referencje, a używanie indeksu jest czymś w rodzaju zarabiania. Czy możemy zrobić lepiej?

Inną opcją jest zainwestowanie odrobiny dodatkowych wysiłków, aby upewnić się, że obiekty nie będą miały wpływu na obiekty Cell. W C++ można by osiągnąć za pomocą wektora wskaźników do komórki zamiast wektora komórek, a w Rust możemy zrobić to samo za pomocą pole:

struct Game { 
    is_running: bool, 
    cells: Vec<Box<Cell>>, 
} 

impl Game { 
    fn construct_cells() -> Vec<Box<Cell>> { 
     ["a", "b", "c"].iter() 
      .map(|n| Box::new(Cell { name: n.to_string() })) 
      .collect() 
    } 

    pub fn new() -> Game { 
     let cells = Game::construct_cells(); 

     Game { 
      is_running: false, 
      cells: cells, 
     } 
    } 
} 

Kosztem dodatkowych środków dla każdego Cell (i dodatkowy nieprawidłowego wskaźnik z Game do każdej komórki), co pozwala na maksymalne skuteczną realizację Player:

struct Player<'a> { 
    cell: &'a Cell, 
} 

impl<'a> Player<'a> { 
    pub fn new(cell: &'a Cell) -> Player<'a> { 
     Player { 
      cell: cell, 
     } 
    } 
    pub fn current_cell_name(&self) -> &str { 
     &self.cell.name 
    } 
} 

, pełne compilable przykład at the playground.

Powiązane problemy