Menu
Coddy logo textTech

Iterando Objetos Trait

Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 47 de 61.

Agora que você pode armazenar diferentes tipos em um Vec<Box<dyn Trait>>, o próximo passo é processar essa coleção. Iterar sobre objetos de trait funciona exatamente como iterar sobre qualquer vetor — você percorre cada elemento e chama métodos definidos pela trait.

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
        Box::new(Circle { radius: 1.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

Quando você itera com for shape in &shapes, cada shape é uma referência a um Box<dyn Shape>. Você pode chamar qualquer método da trait Shape diretamente — o Rust lida com o despacho dinâmico automaticamente, chamando a implementação correta para cada tipo concreto.

Este padrão é poderoso para agregar resultados. Você poderia somar todas as áreas, filtrar formas por tamanho ou transformar a coleção. A principal percepção é que, uma vez que os itens estão em uma coleção de objetos trait, você interage com eles puramente através da interface da trait, independentemente de seus tipos subjacentes.

challenge icon

Desafio

Fácil

Vamos construir uma calculadora de preços para um carrinho de compras que pode conter diferentes tipos de itens! Você criará um sistema onde produtos e serviços — cada um com uma lógica de precificação diferente — podem ser armazenados juntos e processados para calcular um preço total.

Você organizará seu código em dois arquivos:

  • items.rs: Defina uma trait pública Priceable com um método price que recebe &self e retorna um f64. Em seguida, crie duas structs públicas:
    • Product — com campos públicos name (String) e cost (f64). Seu price deve simplesmente retornar o cost.
    • Service — com campos públicos name (String), hourly_rate (f64) e hours (f64). Seu price deve retornar hourly_rate * hours.
  • main.rs: Importe seu módulo de itens e crie um vetor do tipo Vec<Box<dyn Priceable>>. Use as entradas para criar um Product e um Service, adicione-os ao seu vetor, então itere pela coleção para calcular e imprimir o preço total de todos os itens.

O poder dos trait objects brilha aqui — seu código de iteração não precisa saber se está processando um Product ou um Service. Ele simplesmente chama price() em cada item, e o Rust despacha para a implementação correta automaticamente.

Sua saída deve exibir o preço total com uma casa decimal:

Total: ${total}

Por exemplo, com as entradas Laptop, 999.99, Consulting, 150.0 e 3.0:

Total: $1449.99

E com as entradas Book, 29.99, Tutoring, 50.0 e 2.0:

Total: $129.99

Você receberá cinco entradas: o nome do produto, o custo do produto, o nome do serviço, o valor da hora e as horas trabalhadas (converta os valores numéricos como f64).

Folha de consulta

Para iterar sobre uma coleção de objetos trait, use um loop for padrão com uma referência para o vetor:

for item in &collection {
    item.trait_method();
}

Ao iterar com for item in &vec, cada item é uma referência a um Box<dyn Trait>. Você pode chamar qualquer método da trait diretamente — o Rust lida com o despacho dinâmico automaticamente, chamando a implementação correta para cada tipo concreto.

Exemplo de iteração sobre objetos trait para agregar resultados:

trait Shape {
    fn area(&self) -> f64;
}

struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }

impl Shape for Circle {
    fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 { self.width * self.height }
}

fn main() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 2.0 }),
        Box::new(Rectangle { width: 3.0, height: 4.0 }),
    ];

    for shape in &shapes {
        println!("Area: {}", shape.area());
    }
}

Este padrão é útil para agregar resultados — você pode somar valores, filtrar por critérios ou transformar a coleção enquanto interage com os itens puramente através da interface da trait.

Experimente você mesmo

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // Ler entradas
    let mut product_name = String::new();
    std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
    let product_name = product_name.trim().to_string();

    let mut product_cost = String::new();
    std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
    let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");

    let mut service_name = String::new();
    std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
    let service_name = service_name.trim().to_string();

    let mut hourly_rate = String::new();
    std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
    let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");

    let mut hours = String::new();
    std::io::stdin().read_line(&mut hours).expect("Failed to read line");
    let hours: f64 = hours.trim().parse().expect("Failed to parse");

    // TODO: Criar um Product usando product_name e product_cost

    // TODO: Criar um Service usando service_name, hourly_rate e hours

    // TODO: Criar um Vec<Box<dyn Priceable>> e adicionar ambos os itens a ele

    // TODO: Iterar pelo vetor e calcular o preço total

    // TODO: Imprimir o total com uma casa decimal
    // Formato: Total: ${total:.1}
}
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