To understand Rust, I am trying to implement a little formula interpreter.
An expression can only be an integer, a sum, a variable (Term
) or an assignment (Set
). We can then evaluate an expression. Since symbols with no associated values can appear in an expression, its evaluation yields another expression (and not necessarily an integer).
The values of the variables (if there are any) can be found in a hash table.
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,
}
}
}
The above code does not compile. Each match
seems to borrow a variable. Moreover, I think we should not use the String
type, but I can't understand why.
The compilation error:
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>>
This is a second version where I follow Adrian's suggestion to replace all
ExprNode
withRc<ExprNode>
. The only cloned variables areRc
pointers, so I guess this just increments a reference count. My only regret is that we lost the method syntax, but I think this can be repaired by definingExpr
with astruct
instead of a type alias.This question is somewhat subjective, but here are some problems I see:
Here, you can't dereference
a_ev
andb_ev
because*a_ev
is owned by theRc
container holding it. You can fix this error by waiting until you actually need the values to be put inRc
containers to create them:Here, the variable
a
has type&String
, and so writing&a
makes no sense -- it would be like a reference to a reference. That can be fixed by changing&a
toa
. Also,env.get(a).unwrap()
is a reference to aExpr
that is owned by theHashMap
, so you can't dereference/move it. One solution to this problem would be to use aHashMap<String, Rc<Expr>>
instead of aHashMap<String, Expr>
. Another would be to simply clone the value:In order to be able to clone the value, you must use a "derive" compiler directive to say that
Expr
implements that trait:Here, you move
*b_ev
into theHashMap
and then try to dereference/move it again by returning it. Also, like above, you have an extra&
. Both of these issues can be solved in the same way as above:Here, you are moving
**a
intootherwise
while it is still owned by anRc
container. Since you don't useotherwise
, the problem is easily fixed by replacing it with_
:You can't take a value that is owned by something else (
*self
is owned by something else) and return it by value. You can, however, clone it:Overall, the problem with your code is that it tries to duplicate data in a few places. As I said above, there are two ways of fixing it that I can think of: using
Rc<Expr>
everywhere instead ofExpr
, or usingclone
. Here is a fixed version of your code that compiles and usesclone
:[playpen]