dyn Trait

In addition to using traits for static dispatch via generics, Rust also supports using them for type-erased, dynamic dispatch via trait objects:

  1. struct Dog {
  2.     name: String,
  3.     age: i8,
  4. }
  5. struct Cat {
  6.     lives: i8,
  7. }
  8. trait Pet {
  9.     fn talk(&self) -> String;
  10. }
  11. impl Pet for Dog {
  12.     fn talk(&self) -> String {
  13.         format!("Woof, my name is {}!", self.name)
  14.     }
  15. }
  16. impl Pet for Cat {
  17.     fn talk(&self) -> String {
  18.         String::from("Miau!")
  19.     }
  20. }
  21. // Uses generics and static dispatch.
  22. fn generic(pet: &impl Pet) {
  23.     println!("Hello, who are you? {}", pet.talk());
  24. }
  25. // Uses type-erasure and dynamic dispatch.
  26. fn dynamic(pet: &dyn Pet) {
  27.     println!("Hello, who are you? {}", pet.talk());
  28. }
  29. fn main() {
  30.     let cat = Cat { lives: 9 };
  31.     let dog = Dog { name: String::from("Fido"), age: 5 };
  32.     generic(&cat);
  33.     generic(&dog);
  34.     dynamic(&cat);
  35.     dynamic(&dog);
  36. }

This slide should take about 5 minutes.

  • Generics, including impl Trait, use monomorphization to create a specialized instance of the function for each different type that the generic is instantiated with. This means that calling a trait method from within a generic function still uses static dispatch, as the compiler has full type information and can resolve which type’s trait implementation to use.

  • When using dyn Trait, it instead uses dynamic dispatch through a virtual method table (vtable). This means that there’s a single version of fn dynamic that is used regardless of what type of Pet is passed in.

  • When using dyn Trait, the trait object needs to be behind some kind of indirection. In this case it’s a reference, though smart pointer types like Box can also be used (this will be demonstrated on day 3).

  • At runtime, a &dyn Pet is represented as a “fat pointer”, i.e. a pair of two pointers: One pointer points to the concrete object that implements Pet, and the other points to the vtable for the trait implementation for that type. When calling the talk method on &dyn Pet the compiler looks up the function pointer for talk in the vtable and then invokes the function, passing the pointer to the Dog or Cat into that function. The compiler doesn’t need to know the concrete type of the Pet in order to do this.

  • A dyn Trait is considered to be “type-erased”, because we no longer have compile-time knowledge of what the concrete type is.