循环调用函数时Rust借用检查器报错的问题排查
step触发借用检查报错的问题 我写了一个状态机的最小示例,每个状态需要获取deps里的数据引用,有时候是可变引用。问题出在run_loop函数里——只要把循环打开调用step就会编译报错,应该是我没搞懂借用检查器的规则。
相关代码如下:
pub struct StateMachine<'a> { state: State<'a>, deps: Deps, } struct SharedData; struct Deps { shared_data: SharedData, } enum State<'a> { A(A<'a>), B(B<'a>), } struct B<'a> { shared_data: &'a mut SharedData, } impl<'a> B<'a> { pub fn transition(&self, deps: &'a mut Deps) -> Option<State<'a>> { Some(State::A(A { shared_data: &deps.shared_data, })) } pub fn run(&mut self, _: &State) { // do stuff } } struct A<'a> { shared_data: &'a SharedData, } impl<'a> A<'a> { pub fn transition(&self, deps: &'a mut Deps) -> Option<State<'a>> { Some(State::B(B { shared_data: &mut deps.shared_data, })) } pub fn run(&self, _: &State) { // do stuff } } impl<'a> StateMachine<'a> { fn run_loop(state_machine: &'a mut StateMachine<'a>) { // loop { Doesn't compile when uncommented state_machine.step(); // } } pub fn step(&'a mut self) { let mut state = match &self.state { State::A(state) => state.transition(&mut self.deps), State::B(state) => state.transition(&mut self.deps), } .unwrap(); match &mut state { State::A(state) => state.run(&self.state), State::B(state) => state.run(&self.state), }; self.state = state; } }
我正在考虑是否应该放弃使用生命周期,转而使用智能指针。
核心问题:生命周期约束导致的永久借用
你给step函数标注了&'a mut self,这意味着调用step后,StateMachine的可变借用会持续整个'a生命周期——而'a正是StateMachine自身的生命周期参数,这就导致第一次调用step后,state_machine被永久锁住,循环里的第二次调用必然触发借用冲突。
另外,transition方法接收&'a mut Deps也有问题:它要求deps的借用生命周期和State的'a绑定,直接把deps的可变借用锁死在整个'a周期内,进一步加剧了冲突。
解决方案:调整生命周期标注,避免永久借用
1. 移除step函数的'a生命周期约束
把step的签名从pub fn step(&'a mut self)改成pub fn step(&mut self),让借用检查器自动推导更短的生命周期。这样每次循环调用step时,借用只会持续到该次调用结束,不会影响下一次循环。
2. 调整transition方法的生命周期
不需要让transition的deps借用和State的'a绑定,改用匿名生命周期或更短的生命周期:
// 用匿名生命周期简化 pub fn transition(&self, deps: &mut Deps) -> Option<State<'_>> // 或显式标注独立生命周期 pub fn transition<'b>(&self, deps: &'b mut Deps) -> Option<State<'b>>
这样transition返回的State只会持有deps的临时借用,不会锁死整个'a周期。
3. 修复run方法的参数冲突
原代码中run方法接收&State时,self.state还未被替换,会导致同时借用旧状态和新状态的冲突。可以先把旧状态取出:
pub fn step(&mut self) { // 先取出旧状态,避免后续借用冲突 let old_state = std::mem::replace(&mut self.state, State::A(A { shared_data: &self.deps.shared_data })); let new_state = match &old_state { State::A(state) => state.transition(&mut self.deps), State::B(state) => state.transition(&mut self.deps), }.unwrap(); match &mut new_state { State::A(state) => state.run(&old_state), State::B(state) => state.run(&old_state), }; self.state = new_state; }
关于智能指针的选择
如果调整生命周期还是觉得繁琐,或者未来需要更复杂的共享/可变场景,确实可以考虑智能指针:
- 单线程场景用
Rc<RefCell<SharedData>>,实现共享可变访问 - 多线程场景用
Arc<Mutex<SharedData>>
但对于当前的简单状态机,调整生命周期就能解决问题,没必要过早引入智能指针的运行时开销。
内容的提问来源于stack exchange,提问作者segfaultdoctor

