Desafio Rust

Trait objects e polimorfismo

Enunciado

Defina um trait Forma com métodos area() -> f64 e nome() -> String. Implemente para Circulo e Retangulo. Crie um vetor de Box<dyn Forma> com várias formas, calcule a área total e imprima o nome e a área de cada forma. Depois, crie uma função que recebe &dyn Forma e imprime seus dados.

Requisitos

  • Trait Forma com area e nome.
  • Implementar para Circulo (raio) e Retangulo (largura, altura).
  • Usar Vec<Box<dyn Forma>>.
  • Função imprimir_forma(f: &dyn Forma).
  • Calcular e imprimir a área total.

Código inicial

trait Forma {
    fn area(&self) -> f64;
    fn nome(&self) -> String;
}

// implemente Circulo e Retangulo

fn imprimir_forma(f: &dyn Forma) {
    // implemente
}

fn main() {
    // crie o vetor, chame imprimir_forma e calcule a área total
}

Saída esperada

Círculo: área = 78.54
Retângulo: área = 24.00
Círculo: área = 12.57
Área total: 115.11
Ver dica

Use std::f64::consts::PI para o valor de π. Para formatar com duas casas decimais, use {:.2}.

Mostrar solução
trait Forma {
    fn area(&self) -> f64;
    fn nome(&self) -> String;
}

struct Circulo {
    raio: f64,
}

struct Retangulo {
    largura: f64,
    altura: f64,
}

impl Forma for Circulo {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.raio * self.raio
    }
    fn nome(&self) -> String {
        "Círculo".to_string()
    }
}

impl Forma for Retangulo {
    fn area(&self) -> f64 {
        self.largura * self.altura
    }
    fn nome(&self) -> String {
        "Retângulo".to_string()
    }
}

// Função que aceita qualquer tipo que implemente Forma via trait object
fn imprimir_forma(f: &dyn Forma) {
    println!("{}: área = {:.2}", f.nome(), f.area());
}

fn main() {
    // Vetor de trait objects: cada elemento é um Box<dyn Forma>
    let formas: Vec<Box<dyn Forma>> = vec![
        Box::new(Circulo { raio: 5.0 }),
        Box::new(Retangulo { largura: 4.0, altura: 6.0 }),
        Box::new(Circulo { raio: 2.0 }),
    ];

    let mut area_total = 0.0;
    for forma in &formas {
        imprimir_forma(forma.as_ref()); // &Box<dyn Forma> -> &dyn Forma
        area_total += forma.area();
    }

    println!("Área total: {:.2}", area_total);
}

Passo a passo

  1. O trait Forma define os métodos area e nome que todas as formas devem implementar.
  2. Circulo e Retangulo são structs com os campos necessários.
  3. As implementações de Forma calculam a área e retornam o nome.
  4. imprimir_forma recebe &dyn Forma, permitindo qualquer tipo que implemente o trait sem saber o tipo concreto.
  5. No main, criamos um Vec<Box<dyn Forma>> com diferentes formas. O Box é necessário porque dyn Forma é um tipo sem tamanho conhecido (unsized).
  6. Iteramos com for forma in &formas, obtendo &Box<dyn Forma>. Usamos forma.as_ref() para converter para &dyn Forma e passar para imprimir_forma.
  7. A área total é acumulada somando forma.area().

Por que funciona

Box<dyn Forma> é um ponteiro para um valor no heap que implementa Forma. O dyn indica despacho dinâmico: a chamada de método é resolvida em tempo de execução via vtable. Isso permite armazenar tipos diferentes na mesma coleção. A função imprimir_forma aceita &dyn Forma, promovendo polimorfismo sem genéricos.

Erros comuns

  • Esquecer Box no vetor: Vec<dyn Forma> não compila porque dyn Forma não tem tamanho conhecido.
  • Tentar passar &Box<dyn Forma> diretamente para &dyn Forma sem as_ref(): o compilador pode não coagir automaticamente em todos os contextos; use forma.as_ref() ou &**forma.
  • Não implementar todos os métodos do trait: erro de compilação.
  • Usar f64 sem std::f64::consts::PI: não existe constante PI no prelúdio.

Outra forma de resolver

Usar genéricos com impl Trait para despacho estático, mas isso impede armazenar tipos diferentes no mesmo vetor. Para coleções heterogêneas, Box<dyn Trait> é a solução idiomática.

Saída esperada

Círculo: área = 78.54
Retângulo: área = 24.00
Círculo: área = 12.57
Área total: 115.11