0

I have two structs, Dog and Cat:

struct Dog {
    weight: f64
}
struct Cat {
    weight: f64
}

and two traits MakesSound and HasWeight

trait MakesSound {
    fn make_sound(&self);
}

impl MakesSound for Dog {
    fn make_sound(&self) {
        println!("Bark bark!");
    }
}

impl MakesSound for Cat {
    fn make_sound(&self) {
        println!("Go away.");
    }
}

trait HasWeight {
    fn get_weight(&self) -> f64;
}

impl HasWeight for Dog {
    fn get_weight(&self) -> f64 { self.weight }
}

impl HasWeight for Cat {
    fn get_weight(&self) -> f64 { self.weight }
}

I would like to be able to store them in a heterogeneous Vec and then make use of both their traits

trait Animal: MakesSound + HasWeight {}
impl<T: MakesSound + HasWeight> Animal for T {}

fn main() {
    let dog = Dog{ weight: 45.0 };
    let cat = Cat{ weight: 12.0 };
    let animals: Vec<&Animal> = vec![&dog, &cat];
    for animal in animals {
        animal.make_sound();
        println!("{}", animal.get_weight());
        //print_weight(animal as &HasWeight);
    }
}

How would I define a print_weight function that had type

fn print_weight(x: &HasWeight);

so that my function would require as little information as possible, but my Vec is storing as much information as possible?

The error I get from uncommenting the line above is

error: non-scalar cast: `&Animal` as `&HasWeight`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dave
  • 1,031
  • 1
  • 8
  • 23

1 Answers1

2

Here is a print_weight function that is generic over types with the HasWeight trait. Unfortunately, I'm too inexperienced with Rust to tell you why the additional ?Sized trait bound is necessary.

fn print_weight<T: HasWeight + ?Sized>(thing: &T) {
    println!("{}", thing.get_weight());
}

This can be called from within your loop without any casting: print_weight(animal).

Playground link

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Fabian
  • 1,862
  • 13
  • 17
  • That does indeed work although... I thought the Sized trait was a requirement, that the compiler can be shut up by telling it that Sized is optional suggests that the compiler can do without it. I'd be interested to know how and if static dispatch is still occurring. – Dave Jan 29 '17 at 08:04
  • To address both of your concerns: the `?Sized` bound is required for mapping T to unsized types. In particular, when `T = HasWeight`, the function argument becomes a trait object (`&HasWeight`), which is dynamically dispatched. For other cases, the concrete object type behind the argument is known and this one would be statically dispatched. – E_net4 Jan 30 '17 at 10:09