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

Rust可变借用检查疑问:原因解析与代码重构方案咨询

关于Rust借用检查器的限制问题及解决方法

问题描述

最小复现示例

struct Demo<'a>(&'a u64 /* , more fields ... */);

impl<'a> Demo<'a> {
    fn foo(&mut self) -> &u64 {
        // modify self ...
        self.0
    }

    fn bar(&mut self) -> u64 {
        let a: &u64 = self.foo();
        let b: &u64 = self.foo();
        *a + *b
    }
}

编译时触发错误:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/main.rs:11:23
   |
10 |         let a: &u64 = self.foo();
   |                       ---- first mutable borrow occurs here
11 |         let b: &u64 = self.foo();
   |                       ^^^^ second mutable borrow occurs here
12 |         *a + *b
   |         -- first borrow later used here

用户问题

  1. 为何Rust编译器无法识别*a不会修改Demo实例?
  2. 能否在保留原有逻辑的前提下重构代码以避免该错误?

实际场景:解释器开发

pub type Scope<'a> = HashMap<&'a str, Rc<Value<'a>>>;

pub struct Environment<'a> {
    scopes: Vec<Scope<'a>>,
}

struct Interpreter<'a> {
    env: Environment<'a>,
}

fn run_expr(&mut self, expr: &Expression) -> Result<Rc<Value>, Error> {
    match expr {
        // ...
    }
}

fn run_arithmetic(
    &mut self,
    op: &ArithmeticOperator,
    lhs: &Expression,
    rhs: &Expression,
) -> Result<Rc<Value>, Error> {
    let lhs = self.run_expr(lhs)?;
    let rhs = self.run_expr(rhs)?;
    match (lhs.as_ref(), rhs.as_ref()) {
    // ...
}

Interpreter维护存储变量绑定的环境栈,self需为可变类型(部分表达式会修改环境),但执行二元运算时触发上述可变借用冲突。

问题解答

1. 编译器限制的原因

Rust的借用检查器基于函数签名做静态分析,而非函数内部的实际逻辑。foo的签名&mut self -> &u64明确告知编译器:调用该函数需要独占的可变借用,且返回的引用与self的生命周期绑定。

编译器不会去分析foo内部是否真的修改了self,也不会跟踪返回的引用具体指向self的哪个字段——它只会遵循核心规则:只要返回的引用还活着,self的可变借用就不能被释放或再次借用。这种保守性是为了保证内存安全规则的简单、可预测性,避免复杂流分析带来的编译速度下降和学习成本提升。

2. 重构方案

针对Demo示例的重构

方法一:提前消费引用

立即解引用获取值,让引用的生命周期提前结束,释放self的可变借用:

impl<'a> Demo<'a> {
    fn foo(&mut self) -> &u64 {
        // modify self ...
        self.0
    }

    fn bar(&mut self) -> u64 {
        let a_val = *self.foo(); // 解引用后,借用立即释放
        let b_val = *self.foo();
        a_val + b_val
    }
}
方法二:拆分可变操作与引用返回

将修改self的逻辑和返回引用的逻辑分离,避免单次调用同时占用可变借用和返回引用:

impl<'a> Demo<'a> {
    fn modify(&mut self) {
        // 原foo中修改self的逻辑
    }

    fn get_ref(&self) -> &u64 {
        self.0
    }

    fn bar(&mut self) -> u64 {
        self.modify();
        let a = self.get_ref();
        self.modify();
        let b = self.get_ref();
        *a + *b
    }
}
方法三:使用内部可变性

如果场景允许,用RefCell将可变操作移到运行时检查,这样可以用不可变借用调用方法:

use std::cell::RefCell;

struct Demo<'a>(RefCell<&'a u64> /* , more fields ... */);

impl<'a> Demo<'a> {
    fn foo(&self) -> &u64 {
        // 通过RefCell.borrow_mut()修改内部数据
        *self.0.borrow()
    }

    fn bar(&self) -> u64 {
        let a = self.foo();
        let b = self.foo();
        *a + *b
    }
}

针对解释器场景的解决方法

方法一:限制可变借用的作用域

利用块作用域提前释放可变借用,因为Rc<Value>是拥有所有权的类型,不依赖self的借用:

fn run_arithmetic(
    &mut self,
    op: &ArithmeticOperator,
    lhs: &Expression,
    rhs: &Expression,
) -> Result<Rc<Value>, Error> {
    // 块作用域内完成lhs的求值,结束后释放可变借用
    let lhs = {
        let mut this = &mut *self;
        this.run_expr(lhs)?
    };
    // 此时可以再次借用self
    let rhs = self.run_expr(rhs)?;
    match (lhs.as_ref(), rhs.as_ref()) {
        // ...
    }
}
方法二:使用内部可变性包裹环境

这是解释器开发中常用的方案,通过RefCell让Interpreter的方法使用不可变借用,内部处理环境的修改:

use std::cell::RefCell;

struct Interpreter<'a> {
    env: RefCell<Environment<'a>>,
}

fn run_expr(&self, expr: &Expression) -> Result<Rc<Value>, Error> {
    let mut env = self.env.borrow_mut();
    // 原逻辑,修改env
    // ...
}

fn run_arithmetic(
    &self,
    op: &ArithmeticOperator,
    lhs: &Expression,
    rhs: &Expression,
) -> Result<Rc<Value>, Error> {
    let lhs = self.run_expr(lhs)?;
    let rhs = self.run_expr(rhs)?;
    match (lhs.as_ref(), rhs.as_ref()) {
        // ...
    }
}
方法三:分离可变与不可变逻辑

明确区分修改环境的表达式(如赋值)和纯计算表达式,前者用&mut self,后者用&self,避免不必要的可变借用。


内容的提问来源于stack exchange,提问作者yHan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 10:59:55