dyn Trait

На додаток до використання трейтів для статичного пересилання за допомогою узагальнень, Rust також підтримує їх використання для динамічного пересилання зі стиранням типу за допомогою об'єктів трейтів:

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 generic(pet: &impl Pet) {
    println!("Привіт, ви хто? {}", pet.talk());
}

// Використовує стирання типів та динамічну диспетчеризацію.
fn dynamic(pet: &dyn Pet) {
    println!("Привіт, ви хто? {}", pet.talk());
}

fn main() {
    let cat = Cat { lives: 9 };
    let dog = Dog { name: String::from("Фідо"), age: 5 };

    generic(&cat);
    generic(&dog);

    dynamic(&cat);
    dynamic(&dog);
}
This slide should take about 5 minutes.
  • Узагальнення, включаючи impl Trait, використовують мономорфізацію для створення спеціалізованого екземпляру функції для кожного окремого типу, який є екземпляром узагальнення. Це означає, що виклик методу трейта з узагальненої функції все ще використовує статичну диспетчеризацію, оскільки компілятор має повну інформацію про тип і може вирішити, яку саме реалізацію трейта типу слід використовувати.

  • При використанні dyn Trait замість цього використовується динамічна диспетчеризація через віртуальну таблицю методів (vtable). Це означає, що існує єдина версія fn dynamic, яка використовується незалежно від того, який тип Pet передано.

  • При використанні dyn Trait об'єкт трейта повинен знаходитися за якимось посередником. У цьому випадку це буде посилання, хоча також можна використовувати розумні типи вказівників, такі як Box (це буде продемонстровано у день 3).

  • Під час виконання &dyn Pet представляється як "жирний вказівник", тобто пара з двох вказівників: Один вказівник вказує на конкретний об'єкт, який реалізує Pet, а інший вказує на таблицю vtable для реалізації трейту для цього типу. При виклику методу talk на &dyn Pet компілятор шукає вказівник на функцію talk у таблиці vtable, а потім викликає цю функцію, передаючи вказівник на Dog або Cat у цю функцію. Для цього компілятору не потрібно знати конкретний тип Pet.

  • dyn Trait вважається "стертим типом", оскільки під час компіляції ми більше не знаємо, яким є конкретний тип.