In this lesson, you will learn how to build extensible systems by using trait objects to dynamically load and execute modular "plugin" logic.
The Decoupling Pattern
In a plugin architecture, the core application doesn't need to know the internal details of the code it runs. Instead, it relies on a shared trait that defines a common interface. By utilizing Box<dyn Trait>, you can store different implementations in a collection, even if those implementations were compiled in separate crates or loaded at runtime.
The core application "contracts" with the trait. As long as a plugin provides a type that implements that trait, the core can call its methods without caring about the concrete type hidden behind the dynamic dispatch.
Defining the Plugin Interface
Consider a simple data-processing application. We define a Plugin trait that requires an execute method:
trait Plugin {
fn execute(&self, data: &str) -> String;
}
struct LoggerPlugin;
impl Plugin for LoggerPlugin {
fn execute(&self, data: &str) -> String {
format!("Log: {}", data)
}
}
fn run_plugins(plugins: Vec<Box<dyn Plugin>>, data: &str) {
for plugin in plugins {
println!("{}", plugin.execute(data));
}
}
Why this works
By using Box<dyn Plugin>, we move from static dispatch (where the compiler knows exactly which code to run) to dynamic dispatch. The Box allows us to store these objects on the heap, bypassing the requirement that all elements in a Vec must have the same size at compile time.
Because we’ve already explored Object Lifetimes and Dynamic Dispatch costs, you know that these trait objects carry a vtable pointer, which adds a small runtime overhead. However, in exchange, your core system gains the ability to accept new functionality without needing a recompile of the main engine. This is the cornerstone of building scalable, modular Rust applications where you can swap components out like LEGO bricks.
Key Takeaway: By using Box<dyn Trait>, you decouple your application’s core logic from specific implementations, allowing you to treat diverse types as a unified interface for dynamic, modular execution.