Traits
Rust lets you abstract over types with traits. They’re similar to interfaces:
trait Greet { fn say_hello(&self); } struct Dog { name: String, } struct Cat; // No name, cats won't respond to it anyway. impl Greet for Dog { fn say_hello(&self) { println!("Wuf, my name is {}!", self.name); } } impl Greet for Cat { fn say_hello(&self) { println!("Miau!"); } } fn main() { let pets: Vec<Box<dyn Greet>> = vec![ Box::new(Dog { name: String::from("Fido") }), Box::new(Cat), ]; for pet in pets { pet.say_hello(); } }
- 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 Greetis a way to tell the compiler about a dynamically sized type that implementsGreet. The compiler does not know the concrete type that is being passed.- In the example,
petsholds Fat Pointers to objects that implementGreet. The Fat Pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for theGreetimplementation of that particular object.
From rust-lang, “a dyn Trait reference contains two pointers. One pointer goes to the data (e.g., an instance of a struct). Another pointer goes to a map of method call names to function pointers (known as a virtual method table or vtable). At run-time, when a method needs to be called on the dyn Trait, the vtable is consulted to get the function pointer and then that function pointer is called.”
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>>());
This gives the output:
24 0
8 8
16
16