Search code examples
rusttraits

When should I use a trait as a type in rust?


I recently started learning rust and was watching a video about traits and I learned traits were also valid types if dynamically dispatched. What is the point of having a variable with a trait as a type if it has the costs of

  1. Must be heap allocated instead of stack allocated
  2. Has an expensive initialization cost
struct Animal;

trait Roars {
    fn roar(&self);
}

//implement trait roar
impl Roars for Animal {
    fn roar(&self) {
        println!("roarrrrrr")
    }
}

//implement a method rawr
impl Animal {
    fn rawr(&self) {
        println!("rawrrrrrrrrr")
    }
}

fn main() {
    let lion: Box<dyn Roars> = Box::new(Animal);
    let cheeta: Animal = Animal;
    lion.roar();
    cheeta.rawr()
}

>>>roarrrrrr
>>>rawrrrrrrrrr

I tried looking all over google for when this pattern would be a good idea but I couldn't find anything all I know is that it is possible but it comes at a bit of a cost when it comes to code performance so when should i use this pattern?


Solution

  • There are a few places where this pattern is required:

    • You have a container (e.g. Vec) whose items need to be heterogeneous, but must have a minimum of functionality to be useful in the container. The canonical examples in software development tend to be a heterogeneous set of callback functions (expressed Box<dyn Fn> in Rust) or a heterogeneous set of GUI widgets, for example. (What use is a container that can only hold one kind of widget?)
    • You are writing a trait with a method that needs to return a type implementing some trait, but where the actual type cannot be named, which rules out the use of associated types. You often see this with async methods on traits, which cannot yet exist as "async" (or return impl Future) until TAIT is stabilized. Until then, those trait methods need to return Box<dyn Future>.

    There are other places where this pattern can simplify the code substantially. Erasing types with dyn can often eliminate generic type parameters, and sometimes simplifying the code is worth the rather minor runtime cost of an additional heap allocation, vtable lookup, and indirection to the data.