Objetos de Trait Proprietários

Anteriormente vimos como objetos de trait podem ser usados com referências, por exemplo, &dyn Pet. No entanto, também podemos usar objetos de trait com ponteiros inteligentes como Box para criar um objeto de trait owned: 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!("Auau, meu nome é {}", self.name)
    }
}

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

fn main() {
    let pets: Vec<Box<dyn Pet>> = vec![
        Box::new(Cat { lives: 9 }),
        Box::new(Dog { name: String::from("Bidu"), age: 5 }),
    ];
    for pet in pets {
        println!("Olá, quem é você? {}", pet.talk());
    }
}

Layout da memória após alocar pets:

<Dog as Pet>::talk<Cat as Pet>::talkPilhaHeappetsFidoptrtam2capacid.2dataname,4,4age5vtabledatalives9vtable
This slide should take about 10 minutes.
  • Tipos que implementam um dado trait podem ter tamanhos diferentes. Isto torna impossível haver coisas como Vec<dyn Pet> no exemplo anterior.
  • dyn Pet é uma maneira de dizer ao compilador sobre um tipo de tamanho dinâmico que implementa Pet.
  • No exemplo, pets é alocado na pilha e os dados do vetor estão no heap. Os dois elementos do vetor são fat pointers (ponteiros “gordos”):
    • Um fat pointer é um ponteiro de dupla largura. Ele tem dois componentes: um ponteiro para o objeto real e um ponteiro para a tabela de métodos virtuais (vtable) para a implementação Pet desse objeto em particular.
    • Os dados para o Dog chamado Fido são os campos name e age. O Cat tem um campo lives.
  • Compare estas saídas no exemplo anterior::
    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>>());