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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 01:01:20