我在给一个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 :(得分:16)
尽管有外观,但存储在可变矢量中的对象的存储是不安全。请记住,载体可以生长;一旦向量超过其容量,它就会通过分配一个更大的数组并移动其中的所有对象到新位置来增长。对向量中对象的任何引用都将保持悬空状态,并且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 ++中,可以通过使用指针到单元格的向量而不是单元格向量来实现这一点,而在Rust中我们可以通过使用框来实现相同的目标:
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。