rust - 引用向量中的元素

标签 rust

我无法为结构提供一个成员变量,该成员变量是对另一种类型的引用。这是我的结构和实现:

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 组成的向量。当我创建游戏时,我在 construct_cells() 中创建了一个单元格向量,然后我在第一个单元格处启动播放器。我得到的错误是:

expected &Cell, found struct `Cell`

我可以看到我在创建 Player 时没有传递引用,但是如果我将参数更改为 &cells[0] 然后它就会对我大喊大叫借用整个向量,然后在创建 Game 结构时尝试再次使用它。发生什么了?我如何仅向玩家提供对 Cell 的引用?

最佳答案

尽管表面上看,存储对存储在可变向量中的对象的引用是安全的。向量可以增长;一旦向量的长度与其容量匹配,它只能通过分配更大的数组并将其中的所有对象移动到新位置来增长。对其元素的现有引用将悬空,因此 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, cell_idx }
    }
    pub fn current_cell_name(&self) -> &str {
        &self.game.cells[self.cell_idx].name
    }
}

可编译示例 at the playground .

它的缺点是它不允许添加单元格,除非附加它们,因为它会使玩家的索引无效。它还需要通过 Player 对单元格属性的每次访问进行边界检查。 .但是 Rust 是一种具有引用和智能指针的系统语言 - 我们可以做得更好吗?

另一种方法是投入一些额外的努力来确保 Cell对象不受向量重新分配的影响。在 C++ 中,可以通过使用指向单元格的指针向量而不是单元格向量来实现这一点,在 Rust 中,可以通过存储 Box<Cell> 来实现。在向量中。但这不足以满足借用检查器的要求,因为缩小或删除矢量仍会使单元格无效。

这可以使用引用计数的指针来修复,这将允许单元格在向量增长(因为它是在堆上分配的)和收缩时存活下来,因此它不再包含它(因为它不被独占拥有)向量):

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

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

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

        Game {
            is_running: false,
            cells,
        }
    }

    // we could also have methods that remove cells, etc.
    fn add_cell(&mut self, cell: Cell) {
        self.cells.push(Rc::new(cell));
    }
}

以每个 Cell 的额外分配为代价(以及从 Game 到每个单元格的附加指针取消引用),这允许实现 Player比索引更有效:

struct Player {
    cell: Rc<Cell>,
}

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

同样,可编译示例 at the playground .

关于rust - 引用向量中的元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40875152/

相关文章:

rust - 有没有一种方法可以为与基础存储不匹配的类型创建可变引用?

rust - 选项类型的类型转换

vector - 从 Rust 中的向量构建 HashSet

rust - 将新任务添加到 tokio 事件循环并在失败时重试任务

rust - 为什么使用 Reqwest 的 .text() 方法下载的图像会损坏?

rust - 使用 `cfg!` 宏在多个实现之间进行选择的正确方法是什么?

rust - 使用Serde反序列化编号的项目

visual-studio-code - 如何在 Visual Studio Code 的 Rust 项目中查看外部库的源代码?

rust - 打印!同时调用时发生 panic

rust - Rust 中单位类型的用途是什么?