web-dev-qa-db-fra.com

Vecteur d'objets appartenant à un trait

Considérez le code suivant:

trait Animal {
    fn make_sound(&self) -> String;
}

struct Cat;
impl Animal for Cat {
    fn make_sound(&self) -> String {
        "meow".to_string()
    }
}

struct Dog;
impl Animal for Dog {
    fn make_sound(&self) -> String {
        "woof".to_string()
    }
}

fn main () {
    let dog: Dog = Dog;
    let cat: Cat = Cat;
    let v: Vec<Animal> = Vec::new();
    v.Push(cat);
    v.Push(dog);
    for animal in v.iter() {
        println!("{}", animal.make_sound());
    }
}

Le compilateur me dit que v est un vecteur de Animal lorsque j'essaie de pousser cat (incompatibilité de type)

Alors, comment puis-je faire un vecteur d'objets appartenant à un trait et appeler la méthode de trait correspondante sur chaque élément?

46
Jacob Wang

Vec<Animal> n'est pas légal, mais le compilateur ne peut pas vous le dire car le type de correspondance le cache d'une manière ou d'une autre. Si nous supprimons les appels à Push, le compilateur nous donne l'erreur suivante:

<anon>:22:9: 22:40 error: instantiating a type parameter with an incompatible type `Animal`, which does not fulfill `Sized` [E0144]
<anon>:22     let mut v: Vec<Animal> = Vec::new();
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

La raison pour laquelle ce n'est pas légal est qu'un Vec<T> stocke plusieurs T objets consécutivement en mémoire. Cependant, Animal est un trait et les traits n'ont pas de taille (un Cat et un Dog ne sont pas garantis d'avoir la même taille).

Pour résoudre ce problème, nous devons stocker quelque chose qui a une taille dans le Vec. La solution la plus simple consiste à encapsuler les valeurs dans un Box, c'est-à-dire Vec<Box<Animal>>. Box<T> a une taille fixe (un "gros pointeur" si T est un trait, un simple pointeur sinon).

Voici un main fonctionnel:

fn main () {
    let dog: Dog = Dog;
    let cat: Cat = Cat;
    let mut v: Vec<Box<Animal>> = Vec::new();
    v.Push(Box::new(cat));
    v.Push(Box::new(dog));
    for animal in v.iter() {
        println!("{}", animal.make_sound());
    }
}
62
Francis Gagné

Vous pouvez utiliser un objet trait de référence &Animal pour emprunter les éléments et stocker ces objets trait dans un Vec. Vous pouvez ensuite l'énumérer et utiliser l'interface du trait.

Modification du type générique de Vec en ajoutant un & devant le trait fonctionnera:

fn main() {
    let dog: Dog = Dog;
    let cat: Cat = Cat;
    let mut v: Vec<&Animal> = Vec::new();
    //             ~~~~~~~
    v.Push(&dog);
    v.Push(&cat);
    for animal in v.iter() {
        println!("{}", animal.make_sound());
    }
    // Ownership is still bound to the original variable.
    println!("{}", cat.make_sound());
}

C'est très bien si vous souhaitez que la variable d'origine conserve la propriété et la réutilise plus tard.

Gardez à l'esprit le scénario ci-dessus, vous ne pouvez pas transférer la propriété de dog ou cat car le Vec a emprunté ces instances concrètes dans la même étendue.

L'introduction d'une nouvelle portée peut aider à gérer cette situation particulière:

fn main() {
    let dog: Dog = Dog;
    let cat: Cat = Cat;
    {
        let mut v: Vec<&Animal> = Vec::new();
        v.Push(&dog);
        v.Push(&cat);
        for animal in v.iter() {
            println!("{}", animal.make_sound());
        }
    }
    let pete_dog: Dog = dog;
    println!("{}", pete_dog.make_sound());
}
15
nate