Trait Objects and Dynamic Dispatch

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