Trait objects

References

Back to our usage of Rust, instead of using a concrete type for a reference, you can use the dyn keyword, followed by the trait you want as interface. It stands for dynamic dispatch, the mechanism described just before. But however great Rust's type inference normally works, it is (currently) not able to detect if you want to have a trait object reference, so you'll have to define the type manually in this case.

use std::fmt::Display;

fn example() {
  let answer = 42;
  let message = "Hello, world!";

  // Anything that implements the Display trait
  let mut trait_object: &dyn Display = &answer;
  println!("{}", trait_object);
  trait_object = &message;
  println!("{}", trait_object);
}
fn main() { example(); }

Boxed objects

The dyn keyword does only work on references, not on actual objects. That is because of a property very attentive readers might already have noticed way back in the references subchapter, when we only used references on slices, not the slices itself. Slices share the same behaviour as trait objects, and that is not by coincidence as they both do not implement the Sized trait. This trait doesn't do anything by itself, but it's a marker, signaling that the size of a type is known at compile time. As this is needed for translating code to any register- and most stack operations, you can't even assign a non-sized type to a variable. References fix that, as they represent a pointer to memory and are always usize internally, no matter the type of the data they point to. But of course, we want to have owned trait objects too, else there is little sense for polymorphism, isn't it?

Apart from the alternative of using enums, of course there is a way of owning trait objects (and also slices in the process), it's the Box container. The container basically is a owned reference to data on the heap, similar to C++'s unique_ptr, for example. Since allocation is handled at runtime, it doesn't matter if the type inside doesn't implement the Sized trait, whereas the Box itself only saves the reference, so it's Sized itself.

As Box implements the Defref trait, you can call methods on it just like you would on the trait object itself. However, the type inference rule still applies, so if you want your Box to carry a trait object, you have to define the type manually.

use std::fmt::Display;


fn example() {
  // Notice how the object itself isn't a reference anymore!
  let my_vec: Vec<Box<dyn Display>> = vec![Box::new(42), Box::new("Hello, world!")];

  for trait_box in my_vec {
    println!("{}", trait_box);
  }

  // Just so you see it, this works too!
  // Actually doesn't need the type definition since it can be inferred
  // from the return value of the called method
  let boxed_slice: Box<[usize]> = vec![0, 1, 2, 3].into_boxed_slice();
}
fn main() { example(); }