为何NLL无法在match分支中间结束可变借用?
Rust NLL未按预期释放可变引用问题
问题代码
核心代码片段:
fn update(&mut self) -> bool { let now = std::time::Instant::now(); let delta_time = now - self.last_update; self.last_update = now; match self.state { ClientState::MainMenu(ref mut menu) => { // TODO: handle menu transition and rendering true } ClientState::Playing(ref mut game) => { game.update(delta_time); self.render(game) } ClientState::PauseMenu(ref mut menu, ref mut game) => { // TODO: do not update/render game in SP mode while in the menu (full pause) game.update(delta_time); self.render(game) // TODO: handle menu transition and rendering } } } fn render(&self, game: &Game) -> bool { match self.renderer_to_surface.render(game) { Ok(_) => {} Err(wgpu::SurfaceError::Lost) => { self.renderer_to_surface.reconfigure(); } Err(_) => return false, } true }
完整可复现代码:
use std::time::Duration; pub struct Game { pub delta_time: Duration, } pub struct Menu { //... } impl Game { pub fn update(&mut self, delta_time: Duration) -> bool { self.delta_time = delta_time; false } } pub enum ClientState { MainMenu(Menu), Playing(Game), PauseMenu(Menu, Game), } pub struct Client { last_update: std::time::Instant, state: ClientState, } impl Client { /// Return false if there is a fatal error and app should be closed fn update(&mut self) -> bool { let now = std::time::Instant::now(); let delta_time = now - self.last_update; self.last_update = now; match self.state { ClientState::MainMenu(ref mut menu) => { // TODO: handle menu transition and rendering true } ClientState::Playing(ref mut game) => { game.update(delta_time); self.render(game) } ClientState::PauseMenu(ref mut menu, ref mut game) => { // TODO: do not update/render game in SP mode while in the menu (full pause) game.update(delta_time); self.render(game) /// TODO: handle menu transition and rendering } } } fn render(&self, game: &Game) -> bool { // [...] true } }
问题说明
game.update是可变方法,因此在Playing和PauseMenu分支中需要以可变引用访问game。调用该方法后,self.render是不可变方法,不再需要可变借用,但编译器在self.render(game)处报错:self无法被不可变借用,因为ref mut game导致self仍处于可变借用状态。
疑问:既然可变引用在调用update后不再被使用,*NLL(非 lexical lifetimes)*应该在发起新的不可变借用前释放可变借用,为何未生效?
原因分析
Rust中,match分支里通过ref mut game创建的可变借用,其生命周期覆盖整个分支块,而非到最后一次使用game的位置。NLL优化的是普通变量的借用生命周期(即变量最后一次使用后就释放借用),但模式绑定产生的借用生命周期由块作用域决定,即使后续不再使用该引用,借用也会持续到分支结束。
这意味着在self.render(game)执行时,self.state的可变借用(通过ref mut game)仍然有效,而self.render需要对self进行不可变借用,违反了“同一时间只能有一个可变借用或多个不可变借用”的核心规则。
解决方案
方案1:显式缩短可变借用的生命周期
将可变引用的使用限制在子作用域内,让借用在子作用域结束时释放:
ClientState::Playing(ref mut game) => { { let game = game; game.update(delta_time); } // 可变借用在此处释放 self.render(game) }
方案2:显式转换为不可变引用
通过&*game将可变引用转换为不可变引用,让编译器明确知晓可变借用已结束:
ClientState::Playing(ref mut game) => { game.update(delta_time); self.render(&*game) }
方案3:重新匹配获取不可变引用
在调用render前,重新对self.state进行不可变匹配(适用于分支逻辑允许的场景):
ClientState::Playing(ref mut game) => { game.update(delta_time); // 重新匹配获取不可变引用 if let ClientState::Playing(ref game) = self.state { self.render(game) } else { // 理论上不会走到这里,因为刚匹配过Playing状态 true } }
方案2是最简洁且常用的解决方式。
内容的提问来源于stack exchange,提问作者endyx
相关产品推荐
相关产品推荐

