dyn Trait

Além de usar traits para despacho estático via genéricos, o Rust também suporta usá-los para despacho dinâmico, apagamento de tipo, via objetos de trait:

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!("Auau, meu nome é {}", self.name)
    }
}

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

// Usa genéricos e despacho estático.
fn generic(pet: &impl Pet) {
    println!("Olá, quem é você? {}", pet.talk());
}

// Usa apagamento de tipo e despacho dinâmico.
fn dynamic(pet: &dyn Pet) {
    println!("Olá, quem é você? {}", pet.talk());
}

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

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

    dynamic(&cat);
    dynamic(&dog);
}
This slide should take about 5 minutes.
  • Os genéricos, incluindo impl Trait, usam a monomorfização para criar uma instância especializada da função para cada tipo diferente com o qual o genérico é instanciado. Isso significa que chamar um método de trait de dentro de uma função genérica ainda usa despacho estático, pois o compilador tem todas as informações de tipo e pode resolver qual tipo de implementação do trait ele deverá utilizar.

  • Quando se usa dyn Trait, ele usa despacho dinâmico através de uma tabela de métodos virtuais (vtable). Isso significa que há uma única versão de fn dynamic que é usada independentemente do tipo de Pet que é passado.

  • Quando se usa dyn Trait, o objeto de trait precisa estar atrás de algum tipo de indireção. Neste caso, é uma referência, embora tipos de ponteiro inteligente (smart como Box também possam ser usados (isso será demonstrado no dia 3).

  • Em tempo de execução, um &dyn Pet é representado como um “ponteiro gordo”, ou seja, um par de dois ponteiros: Um ponteiro aponta para o objeto concreto que implementa Pet, e o outro aponta para a tabela de métodos virtuais para a implementação do trait para esse tipo. Ao chamar o método talk em &dyn Pet, o compilador procura o ponteiro de função para talk na tabela de métodos virtuais e então invoca a função, passando o ponteiro para o Dog ou Cat para essa função. O compilador não precisa saber o tipo concreto do Pet para fazer isso.

  • Um dyn Trait é considerado “apagado de tipo”, porque não temos mais conhecimento em tempo de compilação sobre qual é o tipo concreto.