Rust中match语句后仍存在借用问题的原因及解决方案
Rust借用错误E0502:原因分析与修复方案
我编写了如下Rust代码,尝试实现一个简单的表达式求值逻辑:
use std::collections::HashMap; type Elem = i64; enum Op { Num(Elem), Add((String, String)), } type Statements = HashMap<String, Op>; fn main() { let mut statements = Statements::new(); statements.insert("lhs".to_string(), Op::Num(64)); statements.insert("op".to_string(), Op::Add(("lhs".to_string(), "rhs".to_string()))); statements.insert("rhs".to_string(), Op::Num(64)); let mut stack = Vec::new(); let mut op_name = "op"; loop { let result: Elem; match statements.get(op_name).unwrap() { Op::Num(_) => { panic!("{}: Did not expect Num", op_name); }, Op::Add((lhs, rhs)) => { if let Op::Num(lhs_value) = statements.get(lhs).unwrap() { if let Op::Num(rhs_value) = statements.get(rhs).unwrap() { result = lhs_value + rhs_value; } else { stack.push(op_name); op_name = rhs; continue; } } else { stack.push(op_name); op_name = lhs; continue; } }, }; if let Some(new_op_name) = stack.pop() { *(statements.get_mut(op_name).unwrap()) = Op::Num(result); op_name = new_op_name; } else { println!("Result: {}", result); return; } } }
编译时触发如下错误:
error[E0502]: cannot borrow `statements` as mutable because it is also borrowed as immutable --> minimal.rs:43:15 | 22 | match statements.get(op_name).unwrap() { | ----------------------- immutable borrow occurs here ... 43 | *(statements.get_mut(op_name).unwrap()) = Op::Num(result); | ^^^^^^^^^^^-------^^^^^^^^^ | | | | | immutable borrow later used by call | mutable borrow occurs here error: aborting due to previous error For more information about this error, try `rustc --explain E0502`.
我原本认为借用会在match语句结束时终止,不清楚match语句后为何仍存在借用。请问该问题的原因是什么?如何修改代码使其正常运行?
错误原因
问题出在借用生命周期的延长上:
- 调用
statements.get(op_name)时,会获取一个对HashMap中Op值的不可变引用(&Op),这个引用的生命周期会覆盖整个match块及其后续代码。 - 匹配
Op::Add((lhs, rhs))时,lhs和rhs是对Op::Add变体中存储的String的引用(&String),这些引用依赖于statements的不可变借用。 - Rust的借用检查器无法智能判断你不会在match块之后使用这些子引用,只会根据代码结构判定:只要子引用存在,
statements的不可变借用就不能结束。这就导致match块后的statements.get_mut可变借用与未结束的不可变借用冲突,触发E0502错误。
修复方案
核心思路是提前复制所需数据,切断对statements的引用依赖,让statements的不可变借用在match块内就结束。以下是两种可行的修改方式:
方式一:提前克隆所需字符串
将match块内的lhs和rhs克隆为独立的String副本,后续操作使用副本而非原引用:
use std::collections::HashMap; type Elem = i64; enum Op { Num(Elem), Add((String, String)), } type Statements = HashMap<String, Op>; fn main() { let mut statements = Statements::new(); statements.insert("lhs".to_string(), Op::Num(64)); statements.insert("op".to_string(), Op::Add(("lhs".to_string(), "rhs".to_string()))); statements.insert("rhs".to_string(), Op::Num(64)); let mut stack = Vec::new(); let mut op_name = "op"; loop { let result: Elem; // 先获取Op引用,克隆需要的字符串 let op = statements.get(op_name).unwrap(); let (lhs, rhs) = match op { Op::Num(_) => panic!("{}: Did not expect Num", op_name), Op::Add((lhs, rhs)) => (lhs.clone(), rhs.clone()), }; match statements.get(&lhs).unwrap() { Op::Num(lhs_value) => { match statements.get(&rhs).unwrap() { Op::Num(rhs_value) => { result = lhs_value + rhs_value; }, _ => { stack.push(op_name); op_name = &rhs; continue; } } }, _ => { stack.push(op_name); op_name = &lhs; continue; } }; if let Some(new_op_name) = stack.pop() { *(statements.get_mut(op_name).unwrap()) = Op::Num(result); op_name = new_op_name; } else { println!("Result: {}", result); return; } } }
方式二:在原match块内直接克隆
保持原有代码结构,在匹配Op::Add时立即克隆lhs和rhs:
// 替换原loop内的match部分 match statements.get(op_name).unwrap() { Op::Num(_) => { panic!("{}: Did not expect Num", op_name); }, Op::Add((lhs, rhs)) => { // 克隆字符串,避免持有原引用 let lhs = lhs.clone(); let rhs = rhs.clone(); if let Op::Num(lhs_value) = statements.get(&lhs).unwrap() { if let Op::Num(rhs_value) = statements.get(&rhs).unwrap() { result = lhs_value + rhs_value; } else { stack.push(op_name); op_name = &rhs; continue; } } else { stack.push(op_name); op_name = &lhs; continue; } }, };
两种方式都能让lhs和rhs脱离对statements的引用依赖,match块结束后不可变借用会被释放,后续的可变借用操作就不会再触发冲突。
内容的提问来源于stack exchange,提问作者Mats L
相关产品推荐
相关产品推荐

