Despacho Dinâmico
Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 45 de 61.
Quando você usa genéricos com limites de trait como <T: Summary>, o Rust determina o tipo exato em tempo de compilação. Isso é chamado de despacho estático—o compilador gera código especializado para cada tipo concreto que você utiliza. É rápido, mas há uma limitação: uma variável só pode conter um tipo específico.
E se você precisar de uma única variável que possa conter tipos diferentes em tempo de execução? É aqui que os trait objects entram. Usando Box<dyn Trait>, você pode armazenar qualquer tipo que implemente a trait:
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 animal: Box<dyn Speak> = Box::new(Dog);
println!("{}", animal.speak()); // "Woof!"
let animal: Box<dyn Speak> = Box::new(Cat);
println!("{}", animal.speak()); // "Meow!"
}
A palavra-chave dyn indica despacho dinâmico — o Rust procura qual método chamar em tempo de execução, em vez de tempo de compilação. O Box é necessário porque objetos de trait não possuem um tamanho conhecido; o Box fornece um ponteiro com um tamanho fixo.
Pense desta forma: os genéricos dizem "Eu trabalho com o tipo T", enquanto objetos de trait dizem "Eu trabalho com qualquer coisa que possa fazer isso". O tradeoff é um pequeno custo de runtime pela flexibilidade de lidar com diferentes tipos através da mesma variável.
Desafio
FácilVamos construir um sistema de aluguel de veículos que demonstra o poder do despacho dinâmico (dynamic dispatch)! Você criará uma trait que define o que qualquer veículo alugável pode fazer e, em seguida, usará Box<dyn Trait> para armazenar diferentes tipos de veículos na mesma variável.
Você organizará seu código em dois arquivos:
vehicle.rs: Defina uma trait públicaRentablecom um métodorental_infoque recebe&selfe retorna umaString. Em seguida, crie duas structs públicas:Car— com um campo públicomodel(String). Seurental_infodeve retornar"Car: {model}"Bike— com um campo públicobrand(String). Seurental_infodeve retornar"Bike: {brand}"
main.rs: Importe seu módulo vehicle e use as entradas para demonstrar o despacho dinâmico. Crie uma variávelBox<dyn Rentable>que primeiro contenha umCar, imprima suas informações de aluguel, depois reatribua a mesma variável para conter umaBikee imprima suas informações de aluguel novamente.
A mágica aqui é que uma única variável do tipo Box<dyn Rentable> pode conter tanto um Car quanto uma Bike — o Rust descobre qual método rental_info chamar em tempo de execução. Isso é o despacho dinâmico em ação!
Sua saída deve mostrar as informações de ambos os veículos:
Car: {model}
Bike: {brand}Por exemplo, com as entradas "Tesla Model 3" e "Trek":
Car: Tesla Model 3
Bike: TrekVocê receberá duas entradas: o modelo do carro e a marca da bicicleta.
Folha de consulta
Rust usa despacho estático com genéricos—o compilador gera código especializado para cada tipo em tempo de compilação. Isso é rápido, mas significa que uma variável só pode conter um tipo específico.
Objetos de trait permitem despacho dinâmico, permitindo que uma única variável contenha tipos diferentes em tempo de execução. Use Box<dyn Trait> para armazenar qualquer tipo que implemente a trait:
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 animal: Box<dyn Speak> = Box::new(Dog);
println!("{}", animal.speak()); // "Woof!"
let animal: Box<dyn Speak> = Box::new(Cat);
println!("{}", animal.speak()); // "Meow!"
}A palavra-chave dyn indica despacho dinâmico—as chamadas de método são resolvidas em tempo de execução. Box é necessário porque objetos de trait não possuem um tamanho conhecido em tempo de compilação.
Diferença chave: Genéricos dizem "Eu funciono com o tipo T" (um tipo específico), enquanto objetos de trait dizem "Eu funciono com qualquer coisa que implemente esta trait" (múltiplos tipos através da mesma variável). A compensação é um pequeno custo em tempo de execução em troca de flexibilidade.
Experimente você mesmo
mod vehicle;
use vehicle::{Rentable, Car, Bike};
fn main() {
// Ler entrada
let mut car_model = String::new();
std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
let car_model = car_model.trim().to_string();
let mut bike_brand = String::new();
std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
let bike_brand = bike_brand.trim().to_string();
// TODO: Criar uma variável Box<dyn Rentable> que contenha um Car
// Imprimir suas informações de aluguel usando o método rental_info()
// TODO: Reatribuir a mesma variável para conter uma Bike
// Imprimir suas informações de aluguel novamente
}
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