Вектор объектов, принадлежащих признаку

Рассмотрим следующий код:

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());
    }
}

Компилятор говорит мне, что v это вектор Animal когда я пытаюсь подтолкнуть cat (несоответствие типов)

Итак, как я могу создать вектор объектов, принадлежащих признаку, и вызвать соответствующий метод признака для каждого элемента?

3 ответа

Решение

Vec<Animal> это недопустимо, но компилятор не может вам этого сказать, потому что несоответствие типов как-то скрывает это. Если мы удалим звонки pushКомпилятор выдает нам следующую ошибку:

<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();
                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Причина, почему это не законно, состоит в том, что Vec<T> хранит много T объекты последовательно в памяти. Тем не мение, Animal это черта, а черты не имеют размера (Cat и Dog не гарантируется иметь одинаковый размер).

Чтобы решить эту проблему, нам нужно хранить что-то, что имеет размер в Vec, Самое простое решение - обернуть значения в Boxт.е. Vec<Box<Animal>>, Box<T> имеет фиксированный размер ("толстый указатель", если T - черта, в противном случае простой указатель).

Вот рабочая main:

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());
    }
}

Существующие ответы объясняют проблему сVec<Animal>хорошо, но они используют старый синтаксис, который больше не действует.

Короче говоря, вектор должен содержать трейт-объекты , а его тип должен быть (что-то вроде)Vec<Box<dyn Animal>>.

В современном Rustключевое слово используется для указания трейт-объекта. Но мы не можем использовать толькоVec<dyn Animal>, потому чтоdyn Animalнет размераDogпотенциально могут иметь поля разного размера). Векторы могут содержать только элементы фиксированного размера. Вот почему в векторе мы должны хранить какие-то указатели на фактические структуры. Boxstruct является одним из таких вариантов, своего рода интеллектуальным указателем , который сам по себе имеет фиксированный размер.

Давайте проверим это (на 64-битной машине):

      use std::mem::size_of;
println!("size Cat = {}", size_of::<Cat>());  // 0 bytes (the Cat struct has no fields)
println!("size Dog = {}", size_of::<Dog>());  // 0 bytes (the Dog struct has no fields)
println!("size BoxCat = {}", size_of::<Box<Cat>>());  // 8 bytes (1 usize pntr)
println!("size BoxDyn = {}", size_of::<Box<dyn Animal>>());  // 16 bytes (2 usize pointers)
println!("{}", size_of::<dyn Animal>());  // Error: doesn't have a size known at compile-time

Обратите внимание, что еслиCatбыли поля,size_of::<Cat>()было бы больше, чем0, ноsize_of::<Box<Cat>>()иsize_of::<Box<dyn Animal>>()вообще не изменится.

Также обратите внимание, чтоBox<dyn Animal>на самом деле содержит 2 указателя:

Теперь к вашему примеру. Чтобы заставить его работать, вам просто нужно заменить эти три строки:

      let v: Vec<Animal> = Vec::new();
v.push(cat);
v.push(dog);    

с этими:

      let mut v: Vec<Box<dyn Animal>> = Vec::new();
v.push(Box::new(cat));
v.push(Box::new(dog));

Вы можете использовать объект эталонной черты &Animal заимствовать элементы и хранить эти черты объектов в Vec, Затем вы можете перечислить его и использовать интерфейс черты.

Изменение Vecобщий тип, добавив & перед чертой будут работать:

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());
}

Это замечательно, если вы хотите, чтобы исходная переменная оставалась владельцем и использовала ее позже.

Имейте в виду, что в приведенном выше сценарии вы не можете передать право собственности на dog или же cat поскольку Vec заимствовал эти конкретные случаи в том же объеме.

Введение новой области может помочь справиться с этой конкретной ситуацией:

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());
}
Другие вопросы по тегам