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

如何移除Rust递归函数中的可变全局静态变量以避免unsafe代码?

重构Rust AST求值函数:移除unsafe与全局状态

问题描述

我正在Rust中编写一个抽象语法树(AST)的求值函数,通过match语句与递归实现逻辑。其中Dup节点需要返回用户输入的最后一个值(由最后执行的Input节点提供)。当前实现使用了可变全局静态变量LAST,依赖unsafe Rust代码,现需重构:

  • 移除unsafe代码
  • LAST的生命周期仅局限于eval函数内部(随函数调用创建和销毁)

尝试过的方案均存在问题:

  • 在函数体内定义变量:递归调用会导致变量在Input分支返回后销毁,无法在递归树中传递状态
  • 添加函数参数:仅适用于向下传递状态,而Input始终是叶子节点,需要向上传递状态

当前实现代码:

static mut LAST: Option<i32>; 

fn eval(ast: AST) -> i32 {
    match ast {
        AST::Operator(op, left, right) => {
            let left_value = eval(*left);
            let right_value = eval(*right);
            
            match op {
                Ops::Mul => left_value * right_value,
            }
        }
        AST::Input => {
            // Prompt the user for input until a valid integer is entered
            loop {
                let mut input = String::new();
                std::io::stdin()
                    .read_line(&mut input)
                    .expect("Failed to read input");
                match input.trim().parse::<i32>() {
                    Ok(value) => {
                        unsafe { LAST = Some(value) };
                        break value
                    },
                    Err(_) => println!("Invalid input! Please enter a valid integer."),
                }
            }
        }
        AST::Dup => {
            unsafe { LAST.unwrap() }
        }
    }
}

解决方案

核心思路是让eval函数同时返回求值结果和最新的输入状态,并将当前状态作为参数传入递归调用,实现状态在整个AST遍历链中的双向传递(向下传递当前状态,向上返回更新后的状态)。这样既不需要全局变量,也完全符合Rust的安全规范,且状态生命周期严格局限于eval的调用过程。

步骤1:定义AST与操作符枚举(补充缺失的定义)

enum Ops {
    Mul,
}

enum AST {
    Operator(Ops, Box<AST>, Box<AST>),
    Input,
    Dup,
}

步骤2:重构eval函数

修改函数参数与返回值,实现状态传递:

fn eval(ast: AST, current_last: Option<i32>) -> (i32, Option<i32>) {
    match ast {
        AST::Operator(op, left, right) => {
            // 先处理左子树,获取左值和左子树处理后的状态
            let (left_val, left_last) = eval(*left, current_last);
            // 用左子树的状态作为初始状态,处理右子树
            let (right_val, right_last) = eval(*right, left_last);
            
            let result = match op {
                Ops::Mul => left_val * right_val,
            };
            // 返回操作结果和右子树处理后的最新状态
            (result, right_last)
        }
        AST::Input => {
            loop {
                let mut input = String::new();
                std::io::stdin()
                    .read_line(&mut input)
                    .expect("Failed to read input");
                match input.trim().parse::<i32>() {
                    Ok(value) => {
                        // 返回输入值,并将状态更新为该值
                        break (value, Some(value));
                    },
                    Err(_) => println!("Invalid input! Please enter a valid integer."),
                }
            }
        }
        AST::Dup => {
            // 使用当前状态作为求值结果,状态保持不变
            (current_last.unwrap(), current_last)
        }
    }
}

步骤3:顶层调用示例

在主函数中调用eval,仅取求值结果即可,状态会随函数调用结束自动销毁:

fn main() {
    // 示例AST:输入一个数,然后乘以它自己(Input * Dup)
    let ast = AST::Operator(
        Ops::Mul,
        Box::new(AST::Input),
        Box::new(AST::Dup),
    );
    
    let (result, _) = eval(ast, None);
    println!("Result: {}", result);
}

可选:更健壮的错误处理

原代码中Dup节点在无输入时会panic,可修改为返回Result类型避免崩溃:

fn eval(ast: AST, current_last: Option<i32>) -> Result<(i32, Option<i32>), &'static str> {
    match ast {
        AST::Operator(op, left, right) => {
            let (left_val, left_last) = eval(*left, current_last)?;
            let (right_val, right_last) = eval(*right, left_last)?;
            
            let result = match op {
                Ops::Mul => left_val * right_val,
            };
            Ok((result, right_last))
        }
        AST::Input => {
            loop {
                let mut input = String::new();
                std::io::stdin()
                    .read_line(&mut input)
                    .expect("Failed to read input");
                match input.trim().parse::<i32>() {
                    Ok(value) => {
                        break Ok((value, Some(value)));
                    },
                    Err(_) => println!("Invalid input! Please enter a valid integer."),
                }
            }
        }
        AST::Dup => {
            current_last.map(|val| (val, current_last))
                .ok_or("Cannot Dup: no previous input")
        }
    }
}

对应的顶层调用:

fn main() {
    let ast = AST::Operator(
        Ops::Mul,
        Box::new(AST::Input),
        Box::new(AST::Dup),
    );
    
    match eval(ast, None) {
        Ok((result, _)) => println!("Result: {}", result),
        Err(e) => eprintln!("Error: {}", e),
    }
}

方案优势

  • 完全移除unsafe代码,符合Rust安全编程规范
  • 状态仅在eval调用链中传递,生命周期严格局限于函数调用过程,调用结束后自动销毁
  • 支持状态的双向传递,既可以向下传递当前最新输入值,也能向上返回更新后的状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 08:39:57