Rust:不可变借用game后如何实现可变借用调用?
解决Rust中的可变/不可变借用冲突问题
问题核心
你遇到的编译错误源于Rust的借用规则:同一时间内,不能对同一个对象同时存在不可变引用和可变引用。这里game.get_player_in_turn()返回了game内部Player的不可变引用,后续调用do_random_move(&mut game)又需要整个game的可变引用,两者冲突导致报错。
可行解决方案
方案一:调整逻辑顺序,避免长期持有不可变引用
先判断当前玩家的类型,再根据类型获取对应可变引用执行操作,同时避免持有整个Player的引用后再尝试借用整个game。
步骤1:新增可变版本的获取当前玩家方法
在Game结构体中添加get_player_in_turn_mut方法:
pub fn get_player_in_turn_mut(&mut self) -> &mut Player { match self.status { Status::InGame(ig_status) => match ig_status { InGameStatus::PlayFirst => { if self.player_a.play_order == PlayOrder::First { &mut self.player_a } else { &mut self.player_b } } InGameStatus::PlaySecond => { if self.player_a.play_order == PlayOrder::Second { &mut self.player_a } else { &mut self.player_b } } }, _ => { panic!("get_player_in_turn_mut called when not in a in-game status"); } } }
步骤2:修改play函数逻辑
先确认游戏状态,再直接获取当前玩家的可变引用,判断类型后执行操作:
fn play(&self, game: &mut crate::game::Game) { // 先确保处于游戏中状态 let ig_status = match &game.status { Status::InGame(s) => *s, _ => panic!("not in game status"), }; // 根据当前回合状态,直接获取玩家类型,不持有长期引用 let is_ai = match ig_status { InGameStatus::PlayFirst => { if game.player_a.play_order == PlayOrder::First { game.player_a.player_kind == PlayerKind::AI } else { game.player_b.player_kind == PlayerKind::AI } } InGameStatus::PlaySecond => { if game.player_a.play_order == PlayOrder::Second { game.player_a.player_kind == PlayerKind::AI } else { game.player_b.player_kind == PlayerKind::AI } } }; if is_ai { let player_in_turn = game.get_player_in_turn_mut(); player_in_turn.do_random_move(game); } else { panic!("not implemented yet"); } game.status = Status::ExitGame; }
方案二:重构do_random_move,缩小可变借用范围
核心思路是避免让do_random_move接收整个game的可变引用,而是只传入它实际需要修改的字段,这样就能避免对整个game的全局可变借用。
步骤1:修改do_random_move的参数
假设AI移动只需要修改游戏状态和对手玩家,调整方法定义:
impl Player { pub fn do_random_move(&mut self, game_status: &mut Status, opponent: &mut Player) { // 原逻辑中对game的操作,替换为对game_status和opponent的操作 *game_status = Status::InGame(InGameStatus::PlaySecond); opponent.health -= 10; } }
步骤2:修改play函数,拆分所需可变引用
在play中直接拆分出do_random_move需要的可变部分,分别传入:
fn play(&self, game: &mut crate::game::Game) { match &mut game.status { Status::InGame(ig_status) => { // 同时获取当前玩家和对手的可变引用 let (current_player, opponent) = match ig_status { InGameStatus::PlayFirst => { if game.player_a.play_order == PlayOrder::First { (&mut game.player_a, &mut game.player_b) } else { (&mut game.player_b, &mut game.player_a) } } InGameStatus::PlaySecond => { if game.player_a.play_order == PlayOrder::Second { (&mut game.player_a, &mut game.player_b) } else { (&mut game.player_b, &mut game.player_a) } } }; match current_player.player_kind { PlayerKind::AI => { current_player.do_random_move(&mut game.status, opponent); } _ => panic!("not implemented yet"), } } _ => panic!("not in game status"), } game.status = Status::ExitGame; }
方案三:使用内部可变性(不推荐作为首选)
如果前两种方案难以实施,可以使用RefCell将编译期借用检查移到运行期,但这种方式可能带来运行时panic风险,需谨慎使用。
步骤1:修改Game结构体的字段类型
use std::cell::RefCell; struct Game { player_a: RefCell<Player>, player_b: RefCell<Player>, status: Status, // 其他字段... }
步骤2:调整get_player_in_turn方法
pub fn get_player_in_turn(&self) -> std::cell::Ref<Player> { match self.status { Status::InGame(ig_status) => match ig_status { InGameStatus::PlayFirst => { if self.player_a.borrow().play_order == PlayOrder::First { self.player_a.borrow() } else { self.player_b.borrow() } } InGameStatus::PlaySecond => { if self.player_a.borrow().play_order == PlayOrder::Second { self.player_a.borrow() } else { self.player_b.borrow() } } }, _ => panic!("get_player_in_turn called when not in a in-game status"), } }
步骤3:修改play函数调用逻辑
fn play(&self, game: &mut crate::game::Game) { let player_in_turn = game.get_player_in_turn(); if player_in_turn.player_kind == PlayerKind::AI { // 获取可变引用执行操作 let mut player_mut = match game.status { Status::InGame(ig_status) => match ig_status { InGameStatus::PlayFirst => { if game.player_a.borrow().play_order == PlayOrder::First { game.player_a.borrow_mut() } else { game.player_b.borrow_mut() } } InGameStatus::PlaySecond => { if game.player_a.borrow().play_order == PlayOrder::Second { game.player_a.borrow_mut() } else { game.player_b.borrow_mut() } } }, _ => panic!("not in game status"), }; player_mut.do_random_move(game); } else { panic!("not implemented yet"); } game.status = Status::ExitGame; }
总结
优先选择方案一或方案二,它们保留了Rust编译期借用检查的安全性,避免运行时风险。方案三仅在架构难以调整时作为备选。
内容的提问来源于stack exchange,提问作者realtebo
相关产品推荐
相关产品推荐

