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

如何处理Rust借用检查过于保守的场景并解决rustc编译报错

报错根本原因

这个报错是Rust当前默认借用检查器的已知局限:你从self.ops获取f时持有的是&Op类型的不可变引用,该引用的生命周期被绑定到self上,后续调用f(self)传入&mut Evaluator时,编译器无法证明这个可变引用不会修改self.ops导致f悬垂,哪怕你的业务逻辑中操作符只会修改栈数据不会改动操作表,编译器也无法做出跨函数的安全校验。


最优解决方案(改动最小,开销极低)

给操作符增加一层引用计数包装,从HashMap中取出时clone一份Rc,即可提前释放对self.ops的借用,完全避免冲突:

use std::rc::Rc;
use std::collections::HashMap;

pub type Value = i32;
pub type Result = std::result::Result<(), Error>;
type Op = Box<dyn Fn(&mut Evaluator) -> Result>;
// 用Rc包裹操作符
type OpTable = HashMap<String, Rc<Op>>;

pub struct Evaluator {
    stack: Vec<Value>,
    ops: OpTable,
}

#[derive(Debug, PartialEq)]
pub enum Error {
    DivisionByZero,
    StackUnderflow,
    UnknownWord,
    InvalidWord,
}

impl Evaluator {
    fn add(&mut self) -> Result {
        if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
            self.stack.push(y + x);
            Ok(())
        } else {
            Err(Error::StackUnderflow)
        }
    }

    fn sub(&mut self) -> Result {
        if let (Some(x), Some(y)) = (self.stack.pop(), self.stack.pop()) {
            self.stack.push(y - x);
            Ok(())
        } else {
            Err(Error::StackUnderflow)
        }
    }

    pub fn new() -> Evaluator {
        let stack: Vec<Value> = vec![];
        let mut ops: OpTable = HashMap::new();
        // 插入时用Rc包裹
        ops.insert("+".to_string(), Rc::new(Box::new(Evaluator::add)));
        ops.insert("-".to_string(), Rc::new(Box::new(Evaluator::sub)));
        Evaluator { stack, ops }
    }

    pub fn eval(&mut self, input: &str) -> Result {
        let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
        // 用户自定义指令
        if let (Some(&":"), Some(&";")) = (symbols.first(), symbols.last()) {
            if symbols.len() > 3 {
                let statement = symbols[2..symbols.len() - 1].join(" ");
                self.ops.insert(
                    symbols[1].to_string().to_ascii_lowercase(),
                    // 自定义操作符也用Rc包裹
                    Rc::new(Box::new(move |caller: &mut Evaluator| caller.exec(&statement))),
                );
                return Ok(());
            } else {
                return Err(Error::InvalidWord);
            }
        }
        self.exec(input)
    }

    fn exec(&mut self, input: &str) -> Result {
        let symbols = input.split_ascii_whitespace().collect::<Vec<_>>();
        for sym in symbols {
            if let Ok(n) = sym.parse::<i32>() {
                self.stack.push(n);
            } else {
                let s = sym.to_ascii_lowercase();
                // cloned()复制一份Rc,复制完成后对self.ops的借用立即释放
                if let Some(f) = self.ops.get(&s).cloned() {
                    f(self)?;
                } else {
                    return Err(Error::UnknownWord);
                }
            }
        }
        Ok(())
    }
}

fn main() {
    let mut e = Evaluator::new();
    e.eval("1 2 +").unwrap();
    println!("{:?}", e.stack); // 输出[3]
    e.eval(": plus-1 1 + ;").unwrap();
    e.eval("4 plus-1").unwrap();
    println!("{:?}", e.stack); // 输出[3,5]
}

Rc的clone仅修改引用计数,开销极低,远低于HashMap的删除插入操作。


其他可选方案

如果是开发阶段测试,你使用Nightly版本Rust的话,可以开启下一代借用检查器Polonius,它可以正确识别你代码中的借用不存在实际冲突,无需修改业务代码即可编译通过:
在项目根目录新建.cargo/config.toml,添加如下配置:

[build]
rustflags = ["-Zpolonius"]

注意该特性目前仍处于开发阶段,不建议在生产环境使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 01:54:08