In this lesson, you will learn how to evaluate complex logic structures by defining a common trait that allows different operations to be executed polymorphically.
The Core Concept: Interpreting Logic
The Interpreter Pattern is used when you have a language or a set of rules you need to evaluate. Instead of writing a massive, hard-to-maintain match statement that tries to handle every possible combination of operations, you define a trait that represents an "expression" capable of evaluating itself.
In Rust, this involves creating a trait—often named Expression—with an evaluate method. By implementing this trait for different structs (like Number, Add, or Subtract), you can nest these objects inside one another to form a tree structure that mimics the logic you want to interpret.
A Practical Example
Imagine we are building a simple calculator. We define an interface that every node in our "syntax tree" must follow:
trait Expression {
fn evaluate(&self) -> i32;
}
struct Number(i32);
impl Expression for Number {
fn evaluate(&self) -> i32 { self.0 }
}
struct Add<L, R> {
left: L,
right: R,
}
impl<L: Expression, R: Expression> Expression for Add<L, R> {
fn evaluate(&self) -> i32 {
self.left.evaluate() + self.right.evaluate()
}
}
When you call evaluate() on the top-level object, it recursively triggers evaluate() on all its children. This "dispatching" happens at runtime or compile-time (depending on whether you use generics or trait objects), allowing complex, deeply nested logic to resolve itself cleanly.
Why use this?
This pattern is incredibly powerful because it separates the structure of your logic (the tree of structs) from the execution of your logic (the evaluate method). If you need to add a new operation, like Multiply, you simply create a new struct and implement the Expression trait. You don't need to change any of the existing logic—you are designing for extension while keeping your architecture modular and type-safe.
Key Takeaway: The Interpreter Pattern turns complex rule sets into recursive trees of objects, delegating evaluation logic to each node via a shared trait interface.