Vetores de Traits
Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 46 de 61.
Você viu como Box<dyn Trait> permite que uma única variável contenha tipos diferentes. O verdadeiro poder surge quando você combina isso com coleções — armazenando múltiplos tipos diferentes em um único vetor.
Um vetor comum exige que todos os elementos sejam do mesmo tipo. Você não pode ter um Vec<Dog> que também contenha gatos. Mas com objetos de trait, você pode criar um vetor que contenha qualquer coisa que implemente uma trait compartilhada:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
O tipo Vec<Box<dyn Speak>> significa "um vetor de objetos trait em boxes". Cada elemento é um Box apontando para algo que implementa Speak—os tipos reais podem diferir. Você envolve cada valor com Box::new() ao adicioná-lo ao vetor.
Este padrão é essencial para construir sistemas flexíveis onde você precisa gerenciar coleções de objetos relacionados, mas diferentes — como componentes de UI, entidades de jogo ou sistemas de plugins.
Desafio
FácilVamos construir um sistema de notificação que possa lidar com diferentes tipos de alertas! Você criará uma coleção que armazena vários tipos de notificação — e-mails, mensagens SMS e notificações push — tudo em um único vetor usando objetos de trait.
Você organizará seu código em dois arquivos:
notifications.rs: Defina uma trait públicaNotifycom um métodosendque recebe&selfe retorna umaStringdescrevendo a notificação que está sendo enviada. Em seguida, crie três structs públicas:Email— com um campo públicorecipient(String). Seusenddeve retornar"Email to: {recipient}"Sms— com um campo públicophone(String). Seusenddeve retornar"SMS to: {phone}"Push— com um campo públicodevice(String). Seusenddeve retornar"Push to: {device}"
main.rs: Importe seu módulo de notificações e crie um vetor do tipoVec<Box<dyn Notify>>que contenha todos os três tipos de notificação. Use as entradas fornecidas para criar um de cada tipo de notificação, adicione-os ao seu vetor na ordem (Email, Sms, Push) e, em seguida, imprima o resultado da chamada desend()no primeiro elemento do vetor.
O conceito principal aqui é que, apesar de Email, Sms e Push serem structs completamente diferentes, todos podem coexistir no mesmo vetor porque compartilham a trait Notify. Cada elemento é envolvido com Box::new() para criar o objeto de trait.
Sua saída deve mostrar a primeira notificação:
Email to: {recipient}Por exemplo, com as entradas "alice@example.com", "555-1234" e "iPhone-12":
Email to: alice@example.comVocê receberá três entradas: o destinatário do e-mail, o número de telefone e o nome do dispositivo.
Folha de consulta
Um Vec<Box<dyn Trait>> permite armazenar múltiplos tipos diferentes em um único vetor, desde que todos implementem a mesma trait.
Vetores regulares exigem que todos os elementos sejam do mesmo tipo, mas objetos de trait (trait objects) permitem coleções heterogêneas:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
];
}
Cada elemento é envolvido com Box::new() ao ser adicionado ao vetor. Os tipos reais podem ser diferentes, desde que implementem a trait compartilhada.
Experimente você mesmo
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Ler as entradas
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: Criar um Vec<Box<dyn Notify>> para conter diferentes tipos de notificação
// TODO: Criar instâncias de Email, Sms e Push usando as entradas
// TODO: Adicioná-las ao vetor em ordem (Email, Sms, Push) usando Box::new()
// TODO: Imprimir o resultado da chamada de send() no primeiro elemento
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
1Métodos e Comportamento
Introdução a Blocos de ImplementaçãoO Parâmetro SelfMétodos MutáveisFunções AssociadasMúltiplos Blocos de ImplementaçãoEncadeamento de MétodosRecapitulação - Ações de Retângulo4Projeto: Pet Virtual
Definindo o PetAlimentando o Pet7Traits Padrão
A Trait DebugA Trait DisplayClone e CopyTraits de IgualdadeRecapitulação - Ponto Imprimível10Projeto: Sistema de Documentos
A Trait DrawComponente de Texto2Encapsulamento e Módulos
Fundamentos de MódulosA Palavra-chave PublicCampos PrivadosGettersSettersRecapitulação - Cofre Seguro5Genéricos
Structs GenéricasMétodos GenéricosMúltiplos Tipos GenéricosFunções GenéricasRecapitulação - Ponto de Coordenada8Traits como Restrições
Sintaxe de Restrições de TraitRestrições MúltiplasA Cláusula WhereRetornando Tipos com TraitsRecapitulação - Impressora Genérica11Padrões de Projeto em Rust
Padrão NewtypeComposiçãoA Trait DropFrom e IntoRecapitulação - Mock de Smart Pointer3Enums Avançados
Enums com DadosMétodos em EnumsCorrespondência de Variantes de DadosO Enum Option RevisitadoRecapitulação - Enum Shape6Definição de Traits
O que é uma Trait?Implementando TraitsImplementações PadrãoSobrescrevendo PadrõesTraits com ParâmetrosRecapitulação - Media Player9Objetos Trait
Despacho DinâmicoVetores de TraitsIterando Objetos TraitRecapitulação - Gerenciador de Zoológico