Вектор объектов, принадлежащих признаку
Рассмотрим следующий код:
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
потенциально могут иметь поля разного размера). Векторы могут содержать только элементы фиксированного размера. Вот почему в векторе мы должны хранить какие-то указатели на фактические структуры. Box
struct является одним из таких вариантов, своего рода интеллектуальным указателем , который сам по себе имеет фиксированный размер.
Давайте проверим это (на 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 указателя:
- тот, который указывает на фактические данные экземпляра структуры;
- один для vtable (это из-за
dyn
; это необходимо для динамической диспетчеризации ).
Теперь к вашему примеру. Чтобы заставить его работать, вам просто нужно заменить эти три строки:
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());
}