Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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ública Rentable com um método rental_info que recebe &self e retorna uma String. Em seguida, crie duas structs públicas:
    • Car — com um campo público model (String). Seu rental_info deve retornar "Car: {model}"
    • Bike — com um campo público brand (String). Seu rental_info deve retornar "Bike: {brand}"
  • main.rs: Importe seu módulo vehicle e use as entradas para demonstrar o despacho dinâmico. Crie uma variável Box<dyn Rentable> que primeiro contenha um Car, imprima suas informações de aluguel, depois reatribua a mesma variável para conter uma Bike e 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: Trek

Você 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
    
}
quiz iconTeste seus conhecimentos

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