10.6 Enums vs. Inheritance in Object-Oriented Programming

OOP programmers might compare Rust enums to class hierarchies. Both model “is-one-of” relationships, but differ in approach.

10.6.1 OOP Approach (Conceptual Example)

OOP uses inheritance and dynamic dispatch (virtual methods):

// Java Example
abstract class Shape { abstract double area(); } // Base class/interface

class Circle extends Shape { /* ... */ @Override double area() { /* ... */ } }
class Rectangle extends Shape { /* ... */ @Override double area() { /* ... */ } }
// Can add Triangle extends Shape later without changing Shape/Circle/Rectangle.

// Usage:
// Shape myShape = new Circle(5.0);
// double area = myShape.area(); // Dynamic dispatch calls Circle.area()
  • Extensibility: Open. New subclasses can be added easily.
  • Polymorphism: Uses dynamic dispatch at runtime.

10.6.2 Rust’s Enum Approach

Rust enums define a closed set of variants, using static dispatch via match:

enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
    // Adding Triangle requires modifying this enum definition
    // and all 'match' expressions handling Shape.
}

impl Shape {
    fn area(&self) -> f64 {
        // Static dispatch: compiler knows which code to run based on variant
        match self {
            Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
            Shape::Rectangle { width, height } => width * height,
            // If Triangle were added, compiler ERRORs until handled here.
        }
    }
}

fn main() {
    let my_shape = Shape::Circle { radius: 5.0 };
    let area = my_shape.area(); // Calls method, uses match internally
    println!("Enum Circle Area: {}", area);
}
  • Fixed Set: Closed. Adding variants requires modifying the enum and related matches (compiler enforces this).
  • Static Dispatch: match determines behavior at compile time. No runtime dispatch overhead.
  • Data & Behavior: Enum lists forms; impl centralizes behavior.

10.6.3 When to Use Enums vs. Trait Objects

  • Use Enums When:

    • The set of variants is fixed and known upfront.
    • You want compile-time exhaustiveness checks.
    • Static dispatch performance is preferred.
    • Modeling variants of a single conceptual type.
  • Use Trait Objects (dyn Trait) When:

    • You need extensibility (adding new types implementing a trait later).
    • You need a heterogeneous collection of different types sharing a trait.
    • Dynamic dispatch is acceptable/required.

Trait objects (Chapter 20) offer dynamic polymorphism closer to the OOP style.