Object Safety and Dyn Compatibility
A trait is dyn-compatible (historically called object-safe) if it can be used as a trait object type dyn Trait (such as &dyn Trait, Box<dyn Trait>, or Arc<dyn Trait>). Starting in recent rustc versions (1.82+ / Rust 2024), compiler diagnostics use the term dyn-compatible (E0038: the trait ... is not dyn compatible) because it directly names the dyn keyword.
Why dyn Trait Requires Vtable-Compatible Items
When a concrete type T is erased behind &dyn Trait, the compiler constructs a static virtual method table (vtable) containing function pointers for each trait method. Because callers only have a two-word fat pointer (data_ptr, vtable_ptr) and do not know the concrete type T or its size at compile time, every vtable method must have a single fixed machine-code signature and known stack argument sizes.
What a `dyn Trait` Vtable Must Know at Compile Time:
+---------------------------------------------------+
| drop_in_place: unsafe fn(*mut ()) |
| size: usize |
| align: usize |
| method_1: fn(*const (), arg1) -> Ret | <-- Fixed signature!
| method_2: fn(*mut (), arg1, arg2) -> Ret | (Cannot be generic `<U>`,
+---------------------------------------------------+ cannot take/return `Self` by value!)
Complete Rules for Dyn Compatibility
A trait Trait is dyn-compatible if and only if both its supertraits and its own items satisfy these rules:
| Trait Item / Property | Dyn-Compatible? | Reason & How to Make Dyn-Compatible |
|---|---|---|
Trait: Sized supertrait bound |
No | dyn Trait is unsized (!Sized) |
Method taking &self, &mut self, Box<Self>, Rc<Self>, Arc<Self>, or Pin<P> |
Yes | Receiver is a pointer with known size (DispatchFromDyn) |
Method taking self by value (fn consume(self)) |
No | Caller does not know how many stack bytes self occupies; add where Self: Sized or take self: Box<Self> |
Method returning Self by value (fn clone(&self) -> Self) |
No | Caller cannot allocate stack space for unknown return size; add where Self: Sized |
Method with Self in another argument (fn eq(&self, other: &Self)) |
No | Two &dyn Trait values could have different underlying concrete types! |
Generic method (fn serialize<W: Write>(&self, w: W)) |
No | A finite vtable cannot hold infinitely many monomorphized <W> function pointers; add where Self: Sized or accept &mut dyn Write |
Associated function without self receiver (fn new() -> Self) |
No | No instance exists from which to read a vtable pointer; add where Self: Sized |
Associated constant (const ID: u32) |
No | Cannot be accessed on dyn Trait; move to a separate trait or method fn id(&self) -> u32 |
Plain associated type (type Output;) |
Yes (if specified) | Must be specified in the trait object type: dyn Trait<Output = u32> |
Generic Associated Type (type Item<'a>;) |
No (if used in vtable methods) | Unbounded family of types cannot be fixed in dyn Trait |
Exempting Non-Dispatched Methods with where Self: Sized
Adding where Self: Sized to a constructor (fn new() -> Self where Self: Sized), a by-value method, or a generic helper method excludes that specific method from the dyn Trait vtable. Concrete types can still call the method normally, while dyn Trait remains completely valid!
trait Plugin {
// Excluded from the `dyn Plugin` vtable via `where Self: Sized`:
fn from_name(name: &str) -> Self
where
Self: Sized;
// Included in the vtable: takes `self: Box<Self>` so a boxed trait object
// can consume itself by value!
fn execute_once(self: Box<Self>) -> String;
// Included in the vtable: standard `&self` receiver:
fn label(&self) -> &str;
}
struct LoggerPlugin {
prefix: String,
}
impl Plugin for LoggerPlugin {
fn from_name(name: &str) -> Self {
Self {
prefix: format!("[{name}]"),
}
}
fn execute_once(self: Box<Self>) -> String {
format!("{} executed and consumed", self.prefix)
}
fn label(&self) -> &str {
&self.prefix
}
}
fn main() {
// Call `from_name` on the concrete type:
let concrete = LoggerPlugin::from_name("audit");
// Erase into `Box<dyn Plugin>`:
let boxed_plugin: Box<dyn Plugin> = Box::new(concrete);
println!("plugin label: {}", boxed_plugin.label());
println!("plugin result: {}", boxed_plugin.execute_once());
}
plugin label: [audit]
plugin result: [audit] executed and consumed