10.6 Enums vs. Inheritance in OOP
In object-oriented languages, inheritance is often used to represent entities that can take on different forms but share common behavior.
10.6.1 OOP Approach
Example in Java:
abstract class Message {
abstract void process();
}
class Quit extends Message {
void process() {
System.out.println("Quit message");
}
}
class Move extends Message {
int x, y;
Move(int x, int y) { this.x = x; this.y = y; }
void process() {
System.out.println("Move to x: " + x + ", y: " + y);
}
}
- Inheritance Hierarchy: Each message type is a subclass.
- Polymorphism: Methods like
process
are overridden.
10.6.2 Rust's Approach with Enums
Rust's enums can model similar behavior without inheritance.
- Single Type: The enum represents all possible variants.
- Pattern Matching: Allows handling each variant appropriately.
- Advantages:
- No Runtime Overhead: No virtual method tables.
- Exhaustiveness Checking: Ensures all cases are handled.
- Safety: Prevents invalid states.
10.6.3 Trait Objects as an Alternative
In Rust, you can use trait objects for polymorphism, but enums are often preferred for their safety and simplicity.
Example Using Traits:
trait Message {
fn process(&self);
}
struct Quit;
impl Message for Quit {
fn process(&self) {
println!("Quit message");
}
}
struct Move {
x: i32,
y: i32,
}
impl Message for Move {
fn process(&self) {
println!("Move to x: {}, y: {}", self.x, self.y);
}
}
fn main() {
let messages: Vec<Box<dyn Message>> = vec![
Box::new(Quit),
Box::new(Move { x: 10, y: 20 }),
];
for msg in messages {
msg.process();
}
}
- Dynamic Dispatch: Using
dyn Message
allows for runtime polymorphism. - Heap Allocation: Each message is boxed, introducing heap allocation.
Note: We will discuss trait objects and their use in Rust in more detail in a later chapter.