You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

循环调用函数时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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 19:37:17