Course contentsShow
← Rust
Lesson 609 of 1,9105. Type System and TraitsFree lesson

Compositional Design via Trait Objects

Assemble modular services by combining trait objects into a cohesive registry.

In this lesson, you will learn how to build modular service registries by grouping different trait objects into a single cohesive structure.

The Modular Registry Pattern

As your application grows, you often need to manage multiple "services"—components that perform tasks like logging, database access, or authentication. Instead of creating a monolithic struct with hard-coded fields, you can use Compositional Design to create a registry that holds trait objects.

By storing Box<dyn Trait> objects, your registry doesn't need to know the concrete types of your services. This allows you to swap implementations (like swapping a ProductionLogger for a MockLogger) without changing the code that consumes the registry.

Building the Registry

Imagine a ServiceRegistry that holds a variety of diagnostic tools. We define a common trait, DiagnosticTool, and store them in a collection:

trait DiagnosticTool {
    fn run(&self) -> String;
}

struct ServiceRegistry {
    tools: Vec<Box<dyn DiagnosticTool>>,
}

impl ServiceRegistry {
    fn new() -> Self {
        Self { tools: Vec::new() }
    }

    fn add_tool(&mut self, tool: Box<dyn DiagnosticTool>) {
        self.tools.push(tool);
    }

    fn run_all(&self) {
        for tool in &self.tools {
            println!("{}", tool.run());
        }
    }
}

Why This Works

The beauty of this design is decoupling. The ServiceRegistry is agnostic to how a tool works; it only cares that the tool implements DiagnosticTool. When you compose your system, you can initialize the registry at runtime, pushing different concrete types into the Vec.

This approach allows you to build a system from smaller, independent "plug-ins." Each service is an isolated module, and the ServiceRegistry acts as the glue that holds these disparate parts together into a unified, functional whole. By leveraging type erasure through Box<dyn Trait>, you keep your architecture flexible and easy to extend, as adding a new service doesn't require modifying the ServiceRegistry internal logic.

Key Takeaway: Compositional design via trait objects allows you to assemble flexible systems by storing heterogeneous service implementations in a unified registry, decoupling the high-level orchestrator from specific implementation details.