struct Constant { value: i32 } struct BinaryPlus { lhs: i32, rhs: i32 } trait Evaluate { fn evaluate(&self) -> i32; } impl Evaluate for Constant { fn evaluate(&self) -> i32 { self.value } } impl Evaluate for BinaryPlus { fn evaluate(&self) -> i32 { self.lhs + self.rhs } } // Adding a new operation is easy. Let's add stringify: trait Stringify { fn stringify(&self) -> String; } impl Stringify for Constant { fn stringify(&self) -> String { format!("{}", self.value) } } impl Stringify for BinaryPlus { fn stringify(&self) -> String { format!("{} + {}", self.lhs, self.rhs) } } // How about adding new types? Suppose we want to add FunctionCall struct FunctionCall { name: String, arguments: Vec<i32> } impl Evaluate for FunctionCall { fn evaluate(&self) -> i32 { todo!() } } impl Stringify for FunctionCall { fn stringify(&self) -> String { todo!() } }
Assuming the whole Stringify section goes into a new file (likewise with FunctionCall) then I agree that this solves the expression problem.