【问题标题】:A simple formula interpreter一个简单的公式解释器
【发布时间】:2015-05-02 15:36:44
【问题描述】:

为了理解 Rust,我正在尝试实现一个小公式解释器。 表达式只能是整数、总和、变量 (Term) 或赋值 (Set)。然后我们可以评估一个表达式。由于没有关联值的符号可以出现在表达式中,因此它的求值会产生另一个表达式(不一定是整数)。

变量的值(如果有的话)可以在哈希表中找到。

use std::rc::Rc;
use std::collections::HashMap;

enum Expr {
    Integer(i32),
    Term(String),
    Plus(Rc<Expr>, Rc<Expr>),
    Set(Rc<Expr>, Rc<Expr>),
}

impl Expr {
    fn evaluate(&self, env: &mut HashMap<String, Expr>) -> Expr {
        match *self {
            Expr::Plus(ref a, ref b) => {
                let a_ev = Rc::new(a.evaluate(env));
                let b_ev = Rc::new(b.evaluate(env));
                match (*a_ev, *b_ev) {
                    (Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
                    _ => Expr::Plus(a_ev, b_ev),
                }
            }
            Expr::Term(ref a) => *env.get(&a).unwrap(),
            Expr::Set(ref a, ref b) => {
                let b_ev = Rc::new(b.evaluate(env));
                match **a {
                    Expr::Term(x) => {
                        let x_value = env.get_mut(&x).unwrap();
                        *x_value = *b_ev;
                        *b_ev
                    }
                    otherwise => {
                        let a_ev = Rc::new(a.evaluate(env));
                        Expr::Set(a_ev, b_ev)
                    }
                }
            }
            otherwise => otherwise,
        }
    }
}

以上代码无法编译。每个match 似乎都借用了一个变量。此外,我认为我们不应该使用String 类型,但我不明白为什么。

编译错误:

error[E0277]: the trait bound `std::string::String: std::borrow::Borrow<&std::string::String>` is not satisfied
  --> src/main.rs:22:39
   |
22 |             Expr::Term(ref a) => *env.get(&a).unwrap(),
   |                                       ^^^ the trait `std::borrow::Borrow<&std::string::String>` is not implemented for `std::string::String`
   |
   = help: the following implementations were found:
             <std::string::String as std::borrow::Borrow<str>>

【问题讨论】:

  • 我建议发布诸如“此代码是否惯用?”之类的问题。到codereview.stackexchange.com
  • @Adrian 我同意,但代码审查需要工作代码,目前看来还不是。
  • 对了,为什么要使用Rc?通常表达式形成树,因此Expr 的唯一所有者是包含它的外部Expr。我认为它不会解决您的任何编译错误,但不必要地使用Rc 肯定不是惯用的。但是,String 在字符串切片不合适或太痛苦时就可以了。
  • @delnan:“通常表达式形成树”。由于共享子表达式,通常是 DAG 的表达式。在这种情况下,一般情况下在编译时没有唯一所有者,因此您需要采用更通用的内存管理形式。在没有跟踪 GC 的情况下,引用计数是下一个最合乎逻辑的选择,尽管它速度较慢并且会泄漏周期。

标签: rust


【解决方案1】:

这个问题有点主观,但我看到了一些问题:


let a_ev = Rc::new(a.evaluate(env));
let b_ev = Rc::new(b.evaluate(env));
match (*a_ev, *b_ev) {
    (Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x+y),
    _ => Expr::Plus(a_ev,b_ev)
}

在这里,您不能取消引用a_evb_ev,因为*a_ev 归拥有它的Rc 容器所有。您可以通过等待直到您真正需要将值放入 Rc 容器来创建它们来修复此错误:

match (a.evaluate(env), b.evaluate(env)) {
    (Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
    (a_ev, b_ev) => Expr::Plus(Rc::new(a_ev), Rc::new(b_ev))
}

Expr::Term(ref a) => *env.get(&a).unwrap()

这里,变量a 的类型为&amp;String,所以写&amp;a 是没有意义的——它就像是对引用的引用。这可以通过将&amp;a 更改为a 来解决。此外,env.get(a).unwrap() 是对 Expr 的引用,该引用由 HashMap 拥有,因此您不能取消引用/移动它。此问题的一种解决方案是使用HashMap&lt;String, Rc&lt;Expr&gt;&gt; 而不是HashMap&lt;String, Expr&gt;。另一种方法是简单地克隆值:

Expr::Term(ref a) => env.get(a).unwrap().clone(),

为了能够克隆值,您必须使用“派生”编译器指令来说明Expr 实现了该特征:

#[derive(Clone)]
enum Expr {   // ...

let b_ev = Rc::new(b.evaluate(env));
match **a {
    Expr::Term(x) => {
        let x_value = env.get_mut(&x).unwrap();
         *x_value = *b_ev;
         *b_ev
    },
    // ...

在这里,您将 *b_ev 移动到 HashMap 中,然后尝试通过返回它再次取消引用/移动它。此外,像上面一样,您还有一个额外的&amp;。这两个问题都可以通过上述相同的方式解决:

let b_ev = b.evaluate(env);
match **a {
    Expr::Term(ref x) => {
        let x_value = env.get_mut(x).unwrap();
        *x_value = b_ev.clone();
        b_ev
    },
    // ...

otherwise => { let a_ev = Rc::new(a.evaluate(env)); // ...

在这里,您将**a 移动到otherwise,而它仍归Rc 容器所有。由于您不使用otherwise,因此将其替换为_ 即可轻松解决问题:

_ => { // ...

otherwise => otherwise

您不能获取属于其他东西的值(*self 属于其他东西)并按值返回。但是,您可以克隆它:

_ => self.clone()

总的来说,您的代码的问题在于它试图在几个地方复制数据。正如我上面所说,有两种方法可以解决它:使用Rc&lt;Expr&gt; everywhere 而不是Expr,或者使用clone。这是您的代码的固定版本,可编译并使用clone

use std::rc::Rc;
use std::collections::HashMap;

#[derive(Clone, Debug)]
enum Expr {
    Integer(i32),
    Term(String),
    Plus(Rc<Expr>, Rc<Expr>),
    Set(Rc<Expr>, Rc<Expr>),
}

impl Expr {
    fn evaluate(&self, env: &mut HashMap<String, Expr>) -> Expr {
        match *self {
            Expr::Plus(ref a, ref b) => {
                match (a.evaluate(env), b.evaluate(env)) {
                    (Expr::Integer(x), Expr::Integer(y)) => Expr::Integer(x + y),
                    (a_ev, b_ev) => Expr::Plus(Rc::new(a_ev), Rc::new(b_ev))
                }
            },
            Expr::Term(ref a) => env.get(a).unwrap().clone(),
            Expr::Set(ref a, ref b) => {
                let b_ev = b.evaluate(env);
                match **a {
                    Expr::Term(ref x) => {
                        let x_value = env.get_mut(x).unwrap();
                        *x_value = b_ev.clone();
                        b_ev
                    },
                    _ => {
                        let a_ev = a.evaluate(env);
                        Expr::Set(Rc::new(a_ev), Rc::new(b_ev))
                    }
                }
            }
            _ => self.clone()
        }
    }
}

fn main() {
    let e = Expr::Plus(Rc::new(Expr::Integer(9)), Rc::new(Expr::Integer(34)));
    let mut env = HashMap::new();

    println!("{:?}", e.evaluate(&mut env));
}

[playpen]

【讨论】:

  • 感谢您的回答,但是我对返回值有些不理解。我认为这是 Rust 中函数返回指针的反模式。
  • 从函数返回指针/引用不一定是个坏主意。通常,在 Rust 中,您将在与在 C/C++ 中返回指针/引用相同的情况下返回指针/引用。例如,您的代码中有返回引用的函数:env.get(a).unwrap() 返回 &amp;Expr
  • 谢谢!如果返回值为Rc 指针,我仍然无法正确获取代码。我想用match (*a.evaluate(env), *b.evaluate(env)) 替换match (a.evaluate(env), b.evaluate(env)) 行,但这些值归Rc 容器所有,无法移动。
  • 表达式*a.evaluate(env) 无效,因为a.evaluate(env) 不是引用或指针。这是Expr
  • 对不起,我的意思是evaluate的原型是fn evaluate(&amp;self, &amp;mut HashMap&lt;String, Expr&gt;) -&gt; Rc&lt;Expr&gt;
【解决方案2】:

这是第二个版本,我按照 Adrian 的建议将所有 ExprNode 替换为 Rc&lt;ExprNode&gt;。唯一克隆的变量是Rc 指针,所以我猜这只是增加了引用计数。我唯一的遗憾是我们丢失了方法语法,但我认为这可以通过使用struct 而不是类型别名来定义Expr 来修复。

use std::rc::Rc;
use std::collections::HashMap;

#[derive(Debug)]
enum ExprNode {
    Integer(i32),
    Term(String),
    Plus(Expr, Expr),
    Set(Expr, Expr),
}

type Expr = Rc<ExprNode>;

type Env = HashMap<String, Expr>;

fn evaluate(e: &Expr, env: &mut Env) -> Expr {
    match **e {
        ExprNode::Plus(ref a, ref b) => {
            let a_ev = evaluate(a, env);
            let b_ev = evaluate(b, env);
            match (&*a_ev, &*b_ev) {
                (&ExprNode::Integer(x), &ExprNode::Integer(y)) => Rc::new(ExprNode::Integer(x + y)),
                _ => Rc::new(ExprNode::Plus(a_ev, b_ev)),
            }
        }
        ExprNode::Term(ref a) => env.get(a).unwrap().clone(),
        ExprNode::Set(ref a, ref b) => {
            let b_ev = evaluate(b, env);
            match **a {
                ExprNode::Term(ref x) => {
                    let x_value = env.get_mut(x).unwrap();
                    *x_value = b_ev;
                    x_value.clone()
                }
                _ => Rc::new(ExprNode::Set(evaluate(a, env), b_ev)),
            }
        }
        _ => e.clone(),
    }
}

fn main() {
    let e = Rc::new(ExprNode::Plus(
        Rc::new(ExprNode::Integer(9)),
        Rc::new(ExprNode::Integer(4)),
    ));
    let mut env = HashMap::new();

    println!("{:?}", evaluate(&e, &mut env));
}

【讨论】:

    猜你喜欢
    • 2010-09-20
    • 2023-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-23
    • 1970-01-01
    相关资源
    最近更新 更多