Course contentsShow
← Rust
Lesson 812 of 1,9106. Generics and Static DispatchFree lesson

Creating Extensible Generic Systems

Use a combination of trait objects and generics to build pluggable, high-performance systems.

In this lesson, you will learn how to design extensible systems by balancing the compile-time performance of generics with the runtime flexibility of trait objects.

When building a pluggable system, you often face a trade-off: Generics (T: Trait) offer high performance through monomorphization (generating specific code for every type), while Trait Objects (&dyn Trait) offer flexibility by allowing you to swap behaviors at runtime. A truly robust system often uses both.

The Hybrid Approach

To build an extensible system, we use generics for the "hot path" (code that runs frequently and benefits from optimization) and store trait objects when we need a collection of diverse, user-provided plugins.

Imagine a game engine where you have a set of System components. You want the main loop to be fast, but you want to allow users to add custom systems at runtime.

trait System {
    fn update(&self);
}

// The Registry stores dynamic plugins
struct Engine {
    plugins: Vec<Box<dyn System>>,
}

impl Engine {
    // A generic method for high-performance internal additions
    fn add_system<T: System + 'static>(&mut self, system: T) {
        self.plugins.push(Box::new(system));
    }

    fn run(&self) {
        for plugin in &self.plugins {
            plugin.update(); // Dynamic dispatch for flexibility
        }
    }
}

Why this works

By using a generic method (add_system<T>) to accept the plugin, we ensure that the user can pass in any concrete type without manually wrapping it in a Box or handling object safety upfront. Inside the method, we perform the "coercion" to a trait object (Box::new(system)).

This design pattern grants you the best of both worlds:

  1. Developer Ergonomics: The API is generic, making it easy to call.
  2. Runtime Extensibility: The internal Vec stores trait objects, allowing you to hold a heterogeneous collection of plugins that can be added or removed while the program is running.
  3. Performance: The "hot" iteration in run uses dynamic dispatch, while the setup logic benefits from the clean, type-safe interface provided by generics.

Key Takeaway: Use generic methods as a "gatekeeper" to accept specific types, and store those types as trait objects internally to enable flexible, pluggable runtime behavior.