Object Safety and Dyn Compatibility
A trait is dyn-compatible (historically called object-safe) if it can be used as a trait object (dyn Trait). Because a trait object erases the concrete Self type at compile time and dispatches calls through a fixed-size vtable, not all traits can be turned into dyn Trait.
Rules for dyn Compatibility
A trait is dyn-compatible if and only if its supertraits are dyn-compatible, it does not require Self: Sized, and every non-Sized-gated item in the trait obeys these rules:
| Trait Item Rule | Allowed in dyn Trait? |
Why? |
|---|---|---|
Methods with &self, &mut self, self: Box<Self>, Rc<Self>, Arc<Self>, or Pin<P> receiver |
Yes | Receiver provides the vtable and data pointer at call time |
Associated functions without a self receiver (fn new() -> Self) |
No (unless gated with where Self: Sized) |
No receiver instance exists from which to look up a vtable |
Methods referencing Self in parameters or return type (fn clone(&self) -> Self) |
No (unless where Self: Sized) |
Caller does not know the compile-time size or concrete type of Self |
Generic methods (fn process<T>(&self, x: T)) |
No (unless where Self: Sized) |
A vtable has a fixed number of slots; it cannot hold infinitely many monomorphized <T> pointers |
Associated constants (const ID: u32;) |
No (unless where Self: Sized) |
Constants are resolved by static type, not through a vtable slot |
impl Trait in method return position (fn iter(&self) -> impl Iterator) |
No | Each implementor returns a differently sized opaque type |
Compiler Error When Using a Non-dyn-Compatible Trait (E0038)
For example, Clone is not dyn-compatible because fn clone(&self) -> Self returns Self by value:
// ❌ does not compile
fn duplicate(item: &dyn Clone) {}
fn main() {}
error[E0038]: the trait `Clone` is not dyn compatible
--> src/main.rs:2:20
|
2 | fn duplicate(item: &dyn Clone) {}
| ^^^^^^^^^^ `Clone` is not dyn compatible
|
= note: the trait is not dyn compatible because it requires `Self: Sized`
Making a Trait dyn-Compatible with where Self: Sized
If a trait needs a constructor, generic helper method, or by-value self method for ergonomic static use, gate those specific methods with where Self: Sized. Methods bounded by where Self: Sized are excluded from the vtable (and cannot be called on dyn Trait), allowing the rest of the trait to be used as dyn Trait:
trait Task {
fn execute(&self) -> String;
// Excluded from vtable via `where Self: Sized`, keeping `Task` dyn-compatible:
fn from_name(name: &str) -> Self
where
Self: Sized;
}
struct BackupTask {
target: String,
}
impl Task for BackupTask {
fn execute(&self) -> String {
format!("Backing up {}", self.target)
}
fn from_name(name: &str) -> Self {
Self {
target: name.to_owned(),
}
}
}
fn run_dynamic(task: &dyn Task) {
println!("{}", task.execute());
}
fn main() {
let task = BackupTask::from_name("/var/lib/db");
run_dynamic(&task);
}
Backing up /var/lib/db