如何处理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
相关产品推荐
相关产品推荐

