如何从递归rust函数中删除一个可变的静态(全局)变量,以防止不安全的使用?

yks3o0rb  于 2023-06-23  发布在  其他
关注(0)|答案(2)|浏览(98)

目前,我正在为rust中的抽象语法树编写一个eval函数(但这对问题来说并不重要),我使用match语句和递归来评估它。Dup节点应该返回用户输入的最后一个值(由最后一个求值的输入节点给出)。下面是我们讨论的函数:

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() }
        }
    }
}

问题是为了在Input和Dup节点之间共享状态,我使用了一个静态和可变的变量,它需要unsafe rust。我想重写它,但不使用unsafe,并且LAST的状态只在fn eval中可用(它的生存期应该在eval()完成时创建和销毁)
我尝试的第一件事是在函数体中添加一个变量,但该函数是递归的,因此只要AST::Input分支返回,值就会消失,并且不会向上传播到递归树。接下来我尝试向函数中添加一个参数,但这只在我向下发送状态时有效,但AST::Input始终是叶节点,因此我们必须向树中发送状态。

xoshrz7s

xoshrz7s1#

Globals几乎总是可以通过将所讨论的global作为函数的参数来替换,如果通信必须双向进行,则将其作为引用:

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

fn eval(ast: AST, mut last: &mut Option<i32>) -> i32 {
    match ast {
        AST::Mul(left, right) => {
            let left_value = eval(*left, last);
            let right_value = eval(*right, last);
            left_value * right_value
        }
        AST::Input => {
            // Prompt the user for input until a valid integer is entered
            use rand::Rng;
            let value = rand::thread_rng().gen();
            *last = Some(value);
            value
        }
        AST::Dup => {
            last.unwrap()
        }
    }
}
svmlkihl

svmlkihl2#

我已经通过跟踪方法签名和返回类型中的状态并在递归调用之前处理状态管理来解决这个问题。不过cafce25的答案似乎更简洁。

fn eval(ast: AST, last: Option<i32>) -> (i32, Option<i32>) {
    match ast {
        AST::Operator(op, left, right) => {
            let left = eval(*left, last);
            let right = eval(*right, left.1);
            let (left_value, right_value) = (left.0, right.0);

            let res = left.1.or_else(|| right.1);

            let result = match op {
                Ops::Mul => left_value * right_value,
            };

            (result, res)
        }
        AST::Input => {
            loop {
                let mut input = String::new();
                std::io::stdin().read_line(&mut input).expect("Failed to read input");
                if let Ok(value) = input.trim().parse::<i32>() {
                    break (value, Some(value));
                } else {
                    println!("Invalid input! Please enter a valid integer.");
                }
            }
        }
        AST::Dup => {
            match last {
                Some(value) => (value, Some(value)),
                None => panic!("Cannot dup without a previous value"),
            }
        }
    }
}

相关问题