Traits

Rust lets you abstract over types with traits. They’re similar to interfaces:

  1. trait Greet {
  2. fn say_hello(&self);
  3. }
  4. struct Dog {
  5. name: String,
  6. }
  7. struct Cat; // No name, cats won't respond to it anyway.
  8. impl Greet for Dog {
  9. fn say_hello(&self) {
  10. println!("Wuf, my name is {}!", self.name);
  11. }
  12. }
  13. impl Greet for Cat {
  14. fn say_hello(&self) {
  15. println!("Miau!");
  16. }
  17. }
  18. fn main() {
  19. let pets: Vec<Box<dyn Greet>> = vec![
  20. Box::new(Dog { name: String::from("Fido") }),
  21. Box::new(Cat),
  22. ];
  23. for pet in pets {
  24. pet.say_hello();
  25. }
  26. }
  • Traits may specify pre-implemented (default) methods and methods that users are required to implement themselves. Methods with default implementations can rely on required methods.
  • Types that implement a given trait may be of different sizes. This makes it impossible to have things like Vec<Greet> in the example above.
  • dyn Greet is a way to tell the compiler about a dynamically sized type that implements Greet.
  • In the example, pets holds Fat Pointers to objects that implement Greet. The Fat Pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the Greet implementation of that particular object.

Compare these outputs in the above example:

println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>()); println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>()); println!("{}", std::mem::size_of::<&dyn Greet>()); println!("{}", std::mem::size_of::<Box<dyn Greet>>());