0

I declared an array of a custom trait Animal in order to experiment with polymorphism in Rust, but the compiler seems to do type inference on the subtype of the first element instead:

fn main() {
    let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
    for single_animal in animals {
        single_animal.talk();
    }
}

trait Animal {
    fn talk(&self);
}

struct Cat;
struct Dog;
struct Lion;

impl Animal for Cat {
    fn talk(&self) { 
        println!("Je miaule !");
    }
}

impl Animal for Dog {
    fn talk(&self) { 
        println!("J'aboie !");
    }
}

impl Animal for Lion {
    fn talk(&self) {
        println!("Je rugit !");
    }
}

The compiler complains about the fact that the first element is a Cat and not the others:

error: mismatched types [--explain E0308]
 --> src/main.rs:3:25
  |>
3 |>     let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>                         ^^^ expected struct `Cat`, found struct `Dog`
note: expected type `Cat`
note:    found type `Dog`

error: mismatched types [--explain E0308]
 --> src/main.rs:3:35
  |>
3 |>     let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>                                   ^^^^ expected struct `Cat`, found struct `Lion`
note: expected type `Cat`
note:    found type `Lion`

error: mismatched types [--explain E0308]
 --> src/main.rs:3:41
  |>
3 |>     let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>                                         ^^^ expected struct `Cat`, found struct `Dog`
note: expected type `Cat`
note:    found type `Dog`

error: mismatched types [--explain E0308]
 --> src/main.rs:3:46
  |>
3 |>     let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>                                              ^^^^ expected struct `Cat`, found struct `Lion`
note: expected type `Cat`
note:    found type `Lion`

error: the trait bound `[Cat; 6]: std::iter::Iterator` is not satisfied [--explain E0277]
 --> src/main.rs:4:5
  |>
4 |>     for single_animal in animals {
  |>     ^
note: `[Cat; 6]` is not an iterator; maybe try calling `.iter()` or a similar method
note: required by `std::iter::IntoIterator::into_iter`

Adding Animal type to the array does not solve the problem either. Because this time I get more errors:

error: mismatched types [--explain E0308]
 --> src/main.rs:3:27
  |>
3 |>     let animals: Animal = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait Animal, found array of 6 elements
note: expected type `Animal`
note:    found type `[Cat; 6]`

error: the trait bound `Animal: std::marker::Sized` is not satisfied [--explain E0277]
 --> src/main.rs:3:9
  |>
3 |>     let animals: Animal = [Cat, Dog, Cat, Lion, Dog, Lion];
  |>         ^^^^^^^
note: `Animal` does not have a constant size known at compile-time
note: all local variables must have a statically known size

error: the trait bound `Animal: std::marker::Sized` is not satisfied [--explain E0277]
 --> src/main.rs:4:5
  |>
4 |>     for single_animal in animals {
  |>     ^
note: `Animal` does not have a constant size known at compile-time
note: required by `std::iter::IntoIterator::into_iter`

error: the trait bound `Animal: std::iter::Iterator` is not satisfied [--explain E0277]
 --> src/main.rs:4:5
  |>
4 |>     for single_animal in animals {
  |>     ^
note: `Animal` is not an iterator; maybe try calling `.iter()` or a similar method
note: required by `std::iter::IntoIterator::into_iter`
George Hilliard
  • 15,402
  • 9
  • 58
  • 96
loloof64
  • 5,252
  • 12
  • 41
  • 78
  • Potential duplicate of http://stackoverflow.com/q/27957103/155423; http://stackoverflow.com/q/25818082/155423; http://stackoverflow.com/q/36357995/155423. – Shepmaster Sep 12 '16 at 14:04
  • I do not totally agree for the duplicate, as vector is a dynamic use, and array declaration in one line showed features and complexities that are specifics to array. – loloof64 Sep 12 '16 at 14:10
  • Nope, none of the answer is specific to an array, other than the literal syntax. `[&Cat as &Animal, &Dog]` and `vec![&Cat as &Animal, &Dog]` work the same, as do `let animals: [&Animal, 2] = [&Cat, &Dog]` and `let animals: Vec<&Animal> = vec![&Cat, &Dog]`. In addition, your question [showed no effort](http://meta.stackoverflow.com/q/261592/155423) towards looking for similar questions, or the potential worry that an array is different from a vector. – Shepmaster Sep 12 '16 at 14:13
  • ok. I was wrong. In fact when editing my question, similar answers were C++ based, not Rust based. – loloof64 Sep 12 '16 at 14:16
  • 1
    Using [Google with a URL restriction](https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=site%3Astackoverflow.com%20rust%20polymorphic%20array) found those 3 on the first page of results for me. I don't always trust the SO search engine or related questions when they come up empty. – Shepmaster Sep 12 '16 at 14:42
  • 1
    Ok, I'll search in Google with URL restriction next time. – loloof64 Sep 12 '16 at 14:46

2 Answers2

7

Rust arrays are homogeneous, which means each element in it has the same type. So you can't have an array with Dogs and Cats. But you can have an array full of so called "trait objects", in your case &Animal. This is how we explicitly request runtime polymorphism.

You have to tell the compiler explicitly that you want an array full of trait objects. The compiler infers the type of the array based on the first element in the initializer, so let's explicitly cast that thing:

let animals = [&Cat as &Animal, &Dog, &Cat, &Lion, &Dog, &Lion];

Note that we also added a & to all values, because you can only work with pointers to trait objects. (another small error in your code is reported afterwards, but the solution is fairly easy). See on the playground.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • 1
    Thanks. So I forgot to use Type Objects (references), to cast the first element to Animal Type Object, and to get an iterator from animals. Thank you very much. My path to Rust learning is growing. – loloof64 Sep 12 '16 at 13:54
  • 2
    @loloof64 Just being pedantic on the terminology: "trait objects", not "type objects". (All objects _must_ have a type, so it's redundant to talk specifically about "objects of a type". Traits, on the other hand are not _concrete_ types by themselves, so a "trait object" has the meaning "an object of a type that implements a certain trait", which is a good way to talk about those) – This company is turning evil. Sep 13 '16 at 11:09
  • 1
    @Kroltan The way you said it is certainly a good way for beginners to think about all this. But actually (being pedantic :P), a trait is a concrete type. You can write `impl MyTrait {}` and use the trait name in every situation where you could use a, say, struct name. The only problem being, that trait types are unsized (do not `impl Sized`), which makes them illegal in many situations. Fun fact: this is why the new "impl trait"-RFC proposes the syntax `impl Trait` instead of just `Trait`, because the latter syntax is already valid. – Lukas Kalbertodt Sep 13 '16 at 11:20
  • 1
    @LukasKalbertodt Yes, I have simplified the meaning of "concrete" too much. What I meant is that you can't have an actual value of type `SomeTrait` directly (because they are unsized), only through trait objects, but then the value itself is "abstracted" (for lack of better word on my vocabulary) behind virtual dispatch, while still being a value of some type that implements `SomeTrait`. – This company is turning evil. Sep 13 '16 at 11:26
3

It's because Cat, Dog and Lion are all different types and you can only have one in an array.

You could use trait objects like Lukas suggested, but what you are after could have been achieved much more easily (trait objects are not something I would recommend to a Rust beginner), with a common Animal enum:

use self::Animal::*;

fn main() {
    let animals = [Cat, Dog, Cat, Lion, Dog, Lion];
    for single_animal in animals.iter() {
        single_animal.talk();
    }
}

trait AnimalSkills {
    fn talk(&self);
}

enum Animal {
    Cat,
    Dog,
    Lion
}

impl AnimalSkills for Animal {
    fn talk(&self) {
        match *self {
            Cat => println!("Je miaule !"),
            Dog => println!("J'aboie !"),
            Lion => println!("Je rugit !")
        }
    }
}

Also note that you need to call .iter() in order to be able to iterate over an array.

ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • Thanks. Don't worry for trait object features, I've already used Java Interfaces, which seems similar. And implementing them does not seems so hard to me. – loloof64 Sep 12 '16 at 14:06
  • 2
    @loloof64 It's not necessarily about whether implementing them is *hard*; it's a question of what's the best tool for the job. You should consider whether Rust offers a better solution before automatically reaching for the solution you are comfortable with in Java. – trent Sep 13 '16 at 00:44
  • Yes you're right. In my case i don't make so much customisation/definition in animal impl. So in this case that seems very much for so few. – loloof64 Sep 13 '16 at 07:23