I don't know why you're getting down voted. But you are right. Rust type system solves this in a very nice way. Maybe to clarify we can show how to do the exact same example shown with Clojure multi-methods, but in Rust:
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!() }
}
The only thing missing here is separation of files.
Assuming the whole Stringify section goes into a new file (likewise with FunctionCall) then I agree that this solves the expression problem.