Polymorphism in rust. Is it possible?

Hi guys. I am trying to get into the polymorphism forest.
Lets say I have a crate which exposes the method print_animal_name_gen, and another create with print_animal_name_dyn.

The question is: can I use both of those methods in the following scenario ??

So far I have errors.

A trait object dyn Animal is unsized. You need to store it behind a pointer like &dyn Animal or Box<dyn Animal> so the size can be tracked. When an unsized object is stored in a pointer Rust widens the pointer to a "fat pointer" that tracks both the location and size of the unsized object.

If you make animal: &dyn Animal then you can assign either &denis or &chip to it. No need to call to_owned() or do any casting.

let animal: &dyn Animal;

if args.len() > 1 && args[1] == "human" {
    animal = &denis;
} else {
    animal = &chip;
}

print_animal_name_gen(animal);
print_animal_name_dyn(animal);

You will have to fix print_animal_name_gen to make this work. When you pass it a &dyn Animal then T is dyn Animal.

As stated upfront, dyn Animal is unsized. That's fine except that generic types are Sized by default: <T> is implicitly <T: Sized>. It's a default bound because most of the time that's what we want. When it's not you have to explicitly opt out of sized-ness by adding a ?Sized bound. T: ?Sized says that T could be sized or unsized, either is fine.

fn print_animal_name_gen<T>(a: &T) where T: Animal + ?Sized,
{
    return println!("{}", a.get_name());
}

Playground

5 Likes

Traits aren't types. But if a trait is dyn-safe, aka object-safe, you can coerce a variable with a type that implements the trait into a dyn Trait (aka a trait object). A dyn Trait is a concrete type. You may want to read this recent introduction.

In addition to that introduction, given your example, I'll also note that the only subtying in Rust is related to lifetimes. In particular, a type that implements a trait is not a subtype of the corresponding dyn Trait, even though it can be coerced into a dyn Trait.


Let's start by cleaning your main function a bit.

     let args: Vec<String> = env::args().collect();
     let denis = Human::new("Denis".to_owned(), 16);
     let chip = Dog::new("Chip".to_owned(), 4);

-    let animal: Animal;
+    // traits aren't types, and `dyn Trait` needs to be behind a pointer
+    // of some kind.  Since this one is owned, let's use a `Box`.
+    let animal: Box<dyn Animal>;

    if args.len() > 1 && args[1] == "human" {
-        animal = denis.to_owned() as Animal;
+        // Unsized coercion to `dyn Animal` will take place automatically
+        // here as we pass the value to `Box::new`.
+        animal = Box::new(denis);
    } else {
-        animal = chip.to_owned() as Animal;
+        // Same idea
+        animal = Box::new(chip);
    }
     print_animal_name_gen(&animal);
-    print_animal_name_dyn(&dyn animal);
+    // `dyn` isn't an operation -- you want to deref the Box
+    // to get to the `dyn Animal` within
+    print_animal_name_dyn(&*animal);

We're closer now, but like that other thread said,

The fix for the latter bullet point is

-fn print_animal_name_gen<T>(a: &T) where T: Animal,
+fn print_animal_name_gen<T>(a: &T) where T: Animal + ?Sized,

And to implement the trait for Box<dyn Animal>, you can do:

impl Animal for Box<dyn Animal + '_> {
    /*
    fn get_name(&self) -> String {
        (**self).get_name()
    }
    */
    fn get_age(&self) -> i32 {
        (**self).get_age()
    }
}

Playground with both.


I left the implementation of get_name commented out as it's a relevant point -- if you don't override the default implementation, you'll get the default implementation, even for Box<dyn Trait>. You generally want to override any default functions for your Box<dyn Trait> implementations, etc.

If you uncomment the get_name implementation, you'll see it works more like you presumably expected.

3 Likes

Thanks guys.
@jkugelman solution is simpler and I have got those results straight away.
@quinedot you have some interesting points I have to rethink about next morning. The magic Box thing is intriguing me. It could save my life someday.

You need the Box when you store an owned list of values whose type is dynamic. That is because you cannot construct a Vec<dyn Animal> but only a Vec<Box<dyn Animal>> or a Vec<&dyn Animal>>, for example. (Playground)

I often forget that I can work with dynamic types when there are references, and that I don't need a Box in those cases. But when you store (i.e. own) the values, you will need a Box.

1 Like