Принадлежні об'єкти трейтів

Раніше ми бачили, як об'єкти трейтів можна використовувати з посиланнями, наприклад, &dyn Pet. Однак, ми також можемо використовувати об'єкти трейтів з розумними вказівниками, такими як Box, щоб створити власний об'єкт трейту: Box<dyn Pet>.

struct Dog {
    name: String,
    age: i8,
}
struct Cat {
    lives: i8,
}

trait Pet {
    fn talk(&self) -> String;
}

impl Pet for Dog {
    fn talk(&self) -> String {
        format!("Гав, мене звуть {}!", self.name)
    }
}

impl Pet for Cat {
    fn talk(&self) -> String {
        String::from("Мяу!")
    }
}

fn main() {
    let pets: Vec<Box<dyn Pet>> = vec![
        Box::new(Cat { lives: 9 }),
        Box::new(Dog { name: String::from("Фідо"), age: 5 }),
    ];
    for pet in pets {
        println!("Привіт, ви хто? {}", pet.talk());
    }
}

Розташування пам’яті після виділення pets:

<Dog as Pet>::talk<Cat as Pet>::talkFidoptrlives9len2capacity2data:name,4,4age5vtablevtablepets: Vec<dyn Pet>data: CatDogProgram textСеткКпуа
This slide should take about 10 minutes.
  • Типи, що реалізують певний трейт, можуть бути різних розмірів. Це унеможливлює створення таких типів, як Vec<dyn Pet> у наведеному вище прикладі.
  • dyn Pet — це спосіб повідомити компілятору про тип динамічного розміру, який реалізує Pet.
  • У прикладі pets розміщується у стеку, а векторні дані - у купі. Два векторні елементи є жирними вказівниками:
    • Жирний вказівник - це вказівник подвійної ширини. Він складається з двох компонентів: вказівника на власне об'єкт і вказівника на таблицю віртуальних методів (vtable) для реалізації Pet цього конкретного об'єкта.
    • Дані для Dog на ім'я Фідо - це поля name та age. Для Cat є поле lives.
  • Порівняйте ці результати в наведеному вище прикладі:
    println!("{} {}", std::mem::size_of::<Dog>(), std::mem::size_of::<Cat>());
    println!("{} {}", std::mem::size_of::<&Dog>(), std::mem::size_of::<&Cat>());
    println!("{}", std::mem::size_of::<&dyn Pet>());
    println!("{}", std::mem::size_of::<Box<dyn Pet>>());