提问人:oui 提问时间:9/7/2023 最后编辑:ShadowRangeroui 更新时间:9/7/2023 访问量:77
在 Rust 中处理大型可变结构的最佳方法是什么?
What is the best way of dealing with a big mutable struct in Rust?
问:
这是我的问题的简化视图:
struct Game {
currentPlayer: u8,
players: Vec<Player>,
units: Vec<Unit>,
}
fn update(game: &mut Game) {
for unit in game.units.iter_mut().filter(|u| u.owner == game.currentPlayer) {
if unit.repairPrice < game.getPlayer(game.currentPlayer).money {
unit.health = 100;
game.getPlayer(game.currentPlayer).money -= unit.repairPrice;
}
}
}
impl Game {
pub fn getPlayer(&mut self, player: u8) -> &mut Player {
return self.players.iter_mut().find(|p| p.color == player).unwrap();
}
}
在这里,编译器说我不能借用值 currentPlayer 作为不可变的,因为 game 已经被借用为可变的。
虽然我理解编译器在说什么以及为什么这么说,但我不知道我应该改变什么。单位和玩家都必须同时更新,所以似乎无论我怎么做,它总是会引起冲突。
我可以克隆整个结构,对其进行迭代和计算,然后在原始可变结构中找到相应的单位和玩家来更新它们,但效率非常低。
答:
将您的代码扩展为针对您的问题的重现器后,很明显,如果没有以下功能,您就无法完成这项工作:
- 实质性的重构,
- 很多浪费的克隆和/或东西,或者
unsafe
- 内联 into 代码(冗余、可怕)
getPlayer
update
根本问题是,当一个函数说它可变地借用一个对象时,它借用了整个对象。因此,即使你知道它只触及属性,Rust 的借用检查器(它适用于函数接口,而不是内容)也必须假设它可以对它收到的引用做任何事情。getPlayer
players
Game
我能想到的最小的解决方案是使它成为一个自由函数,它只接受对对象的可变引用,因此可以以一种明确的方式进行分析,使其既不会被多次借用。getPlayer
Vec
Player
update
Vec
fn update(game: &mut Game) {
for unit in game.units.iter_mut().filter(|u| u.owner == game.currentPlayer) {
// Passed mutable reference to the players only, rather than being called on a whole Game
if unit.repairPrice < getPlayer(&mut game.players, game.currentPlayer).money {
unit.health = 100;
// Same change as above
getPlayer(&mut game.players, game.currentPlayer).money -= unit.repairPrice;
}
}
}
// Made a free function so it can accept the Vec alone, not the whole game
fn getPlayer(players: &mut Vec<Player>, player: u8) -> &mut Player {
return players.iter_mut().find(|p| p.color == player).unwrap();
}
这很好用(尽管它确实抱怨您的非惯用变量名称; FTW,这些废话都不是)。snake_case
camelCase
或者,将 getPlayer
内联到 update
中;它不需要太多余,因为您可以缓存结果并且只复制一次代码,而不是两次(作为奖励,这可能意味着只做一次工作,而不是每个与该播放器关联的单位两次);根本不需要,您只需调整:getPlayer
update
fn update(game: &mut Game) {
let player = game.players.iter_mut().find(|p| p.color == game.currentPlayer).unwrap();
for unit in game.units.iter_mut().filter(|u| u.owner == game.currentPlayer) {
if unit.repairPrice < player.money {
unit.health = 100;
player.money -= unit.repairPrice;
}
}
}
也就是说,我认为这里更好的方法可能是将一个附加到每个 ,而不是打开它,而不是与玩家分开管理单位。这样一来,就不会发生冲突,因为你只需可变地获取单个对象,并对附加到它的 s 进行操作(不触及 的任何其他元素,并且操作效率更高,因为您不需要继续查找玩家,不需要处理每个单元,只需要处理该玩家的单元等)。如果你需要把所有的 s 作为迭代器,一个简单的方法可以让你迭代玩家,并将他们的单位组合成一个逻辑迭代器。Vec<Unit>
Player
Game
getPlayer
update
Player
unit
Game
Unit
flat_map
评论
Game
Game
Game
game.units
game
game.units
评论
let mut player = game.getPlayer(game.currentPlayer);
getPlayer
game.getPlayer(game.currentPlayer)
game.units
filter
game.currentPlayer