I'd like to improve how ownership is handled within a simple interpreter written in Rust. The interpreter takes simple calculator like expressions and returns either a string of the AST or a number of the evaluation:
// Simple expression for working on numbers
enum Exp {
Add { e1 : Box <Exp>, e2 : Box <Exp> },
Mul { e1 : Box <Exp>, e2 : Box <Exp> },
Int { i : i32}
}
// Evaluate an expression to a number
fn exp_to_num(e : Exp) -> i32 {
match e {
Exp::Add {e1,e2} => return exp_to_num(*e1) + exp_to_num(*e2),
Exp::Mul {e1,e2} => return exp_to_num(*e1) * exp_to_num(*e2),
Exp::Int {i} => return i
}
}
// Evaluate an expression to a string
fn exp_to_str(e : Exp) -> String {
match e {
Exp::Add {e1,e2} =>
return format!("({}) + ({})",exp_to_str(*e1),exp_to_str(*e2)),
Exp::Mul {e1,e2} =>
return format!("({}) * ({})",exp_to_str(*e1),exp_to_str(*e2)),
Exp::Int {i} =>
return format!("{}",i)
}
}
fn main() {
// Create an expression
let e1 = Exp::Add {
e1 : Box::new(Exp::Mul {
e1 : Box::new(Exp::Int { i : 2 }),
e2 : Box::new(Exp::Int { i : 3 })}),
e2 : Box::new(Exp::Int {i : 4})};
// Evaluate the expression
println!("e1 : {}", exp_to_num(e1));
//println!("e1 : {}", exp_to_str(e1));
}
The last command can not be uncommented since it appears as though exp_to_num
takes ownership of e1
. I'd like to fix this.
In C++, I'd just make the arguments const &
. In Rust, my best attempt at this is the following code:
// Simple expression for working on numbers
enum Exp {
Add { e1 : Box <Exp>, e2 : Box <Exp> },
Mul { e1 : Box <Exp>, e2 : Box <Exp> },
Int { i : i32}
}
// Evaluate an expression to a number
fn exp_to_num(e : & Exp) -> i32 {
match *e {
Exp::Add {ref e1,ref e2} => return exp_to_num(&*e1) + exp_to_num(&*e2),
Exp::Mul {ref e1,ref e2} => return exp_to_num(&*e1) * exp_to_num(&*e2),
Exp::Int {i} => return i
}
}
// Evaluate an expression to a string
fn exp_to_str(e : & Exp) -> String {
match *e {
Exp::Add {ref e1,ref e2} =>
return format!("({}) + ({})",exp_to_str(&*e1),exp_to_str(&*e2)),
Exp::Mul {ref e1,ref e2} =>
return format!("({}) * ({})",exp_to_str(&*e1),exp_to_str(&*e2)),
Exp::Int {i} =>
return format!("{}",i)
}
}
fn main() {
// Create an expression
let e1 = Exp::Add {
e1 : Box::new(Exp::Mul {
e1 : Box::new(Exp::Int { i : 2 }),
e2 : Box::new(Exp::Int { i : 3 })}),
e2 : Box::new(Exp::Int {i : 4})};
// Evaluate the expression
println!("e1 : {}", exp_to_num(&e1));
println!("e1 : {}", exp_to_str(&e1));
}
Though this works, it feels verbose. Specifically, it feels verbose to match references, Exp::Add {ref e1,ref e2}
, and verbose to find a reference to an unboxed expression, exp_to_num(&*e1)
. Can passing a constant reference be made more concise and compact?
Alternatively, we could just clone the memory, which gives
// Simple expression for working on numbers
#[derive(Clone)]
enum Exp {
Add { e1 : Box <Exp>, e2 : Box <Exp> },
Mul { e1 : Box <Exp>, e2 : Box <Exp> },
Int { i : i32}
}
// Evaluate an expression to a number
fn exp_to_num(e : Exp) -> i32 {
match e {
Exp::Add {e1,e2} => return exp_to_num(*e1) + exp_to_num(*e2),
Exp::Mul {e1,e2} => return exp_to_num(*e1) * exp_to_num(*e2),
Exp::Int {i} => return i
}
}
// Evaluate an expression to a string
fn exp_to_str(e : Exp) -> String {
match e {
Exp::Add {e1,e2} =>
return format!("({}) + ({})",exp_to_str(*e1),exp_to_str(*e2)),
Exp::Mul {e1,e2} =>
return format!("({}) * ({})",exp_to_str(*e1),exp_to_str(*e2)),
Exp::Int {i} =>
return format!("{}",i)
}
}
fn main() {
// Create an expression
let e1 = Exp::Add {
e1 : Box::new(Exp::Mul {
e1 : Box::new(Exp::Int { i : 2 }),
e2 : Box::new(Exp::Int { i : 3 })}),
e2 : Box::new(Exp::Int {i : 4})};
// Evaluate the expression
println!("e1 : {}", exp_to_num(e1.clone()));
println!("e1 : {}", exp_to_str(e1.clone()));
}
This also feels somewhat verbose because we have to call clone rather than just allowing an implicit copy. Is a clone required due to Box
? Can this be made more concise?