2016-11-29 2 views
7

У меня возникли проблемы с предоставлением struct переменной-членом, которая является ссылкой на другой тип. Вот моя структура и реализация:.Ссылка на элемент в векторе

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

Игрок имеет ссылку на текущий Cell, что они находятся в этом моя Game структура и ее реализация:

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 является вектором Cell s. Когда я создаю игру, я создаю вектор ячеек в construct_cells(), а затем запускаю плеер в первой ячейке. Ошибка я получаю:

expected &Cell, found struct `Cell` 

Я могу видеть, что я не передавая ссылку, когда я создаю Player, но если изменить параметр &cells[0] то орет на меня заимствование всего вектора и затем пытаюсь использовать его снова, когда создаю структуру Game. Так, что происходит? Как мне просто дать игроку ссылку на Cell?

+0

У вас есть хорошее представление о том, какие ссылки (или указатели), в частности, что такое * болтающаяся ссылка * (или указатель)? Что произойдет, если вы дадите другу лист бумаги с вашим адресом на нем (123 Main St.), а затем ** вы переехали **?Как будут чувствовать себя новые владельцы дома, если ваш друг только что появился и начал разговаривать с людьми в доме, в котором вы жили, как будто они были вами? – Shepmaster

+0

@Shepmaster Я знаю, какой висячий указатель. Наверное, я просто не понимал, что это решение может позволить это. Как же мне получить тот же результат? Это то, что я пытаюсь сделать возможным? – Dooskington

+1

* Я просто не понимал, что это решение может позволить одно * - и именно поэтому Rust - прекрасный язык, ИМО. Когда вы перемещаете «ячейки» из локальной переменной в структуру «Игра», вы аннулировали любые ссылки в векторе, например, тот, который вы дали «игроку». Языки, такие как C или C++, позволили бы вам это сделать, AFAIK и позволить коду сбой во время выполнения. Трудно сказать, что вы действительно хотите сделать, поскольку «игрок» никогда не используется. – Shepmaster

ответ

10

Несмотря на внешний вид, сохранение ссылки на объект, хранящийся в изменяемом векторе, является не безопасным. Помните, что векторы могут расти; как только вектор превышает его емкость, он выращается путем выделения большего массива и , перемещая все объекты внутри него в новое место. Любые ссылки на объекты в векторе будут оставлены свисающими, а Rust не позволит этого произойти. (Кроме того, вектор может быть сжат или очищен, и в этом случае любые ссылки на его элементы, очевидно, указывают на освобожденную память.) Такая же проблема будет существовать с C++ std::vector.

Существует несколько способов обойти его. Одним из них является, чтобы перейти от прямой ссылки на Cell в безопасный обратный отсчет, состоит из обратного указателя на Game и индекс к вектору элементу:

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

Полных компилируемый пример at the playground.

Вышеупомянутое может выглядеть как обман. В конце концов, Rust - это системный язык, который имеет ссылки, а использование индекса - это некоторая ошибка. Мы можем сделать лучше?

Другой вариант заключается в том, чтобы вложить немного дополнительных усилий, чтобы гарантировать, что объекты Cell не будут затронуты перераспределением векторов. В C++ можно было бы достичь, используя вектор указателей к клетке вместо вектора клеток, и в Русте мы можем сделать то же самое, используя коробки:

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

Цены дополнительное распределение для каждого Cell (и дополнительный указатель разыменования от Game к каждой ячейке), это позволяет максимально эффективную реализацию 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 
    } 
} 

Опять же, полный компилируемый пример at the playground.