Menu
Coddy logo textTech

Iteración de objetos de Trait

Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 47 de 61.

Ahora que puedes almacenar diferentes tipos en un Vec<Box<dyn Trait>>, el siguiente paso es procesar esa colección. Iterar sobre objetos de rasgo funciona igual que iterar sobre cualquier vector: recorres cada elemento y llamas a los métodos definidos por el rasgo.

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());
    }
}

Cuando iteras con for shape in &shapes, cada shape es una referencia a un Box<dyn Shape>. Puedes llamar a cualquier método del trait Shape directamente; Rust maneja el despacho dinámico automáticamente, llamando a la implementación correcta para cada tipo concreto.

Este patrón es potente para agregar resultados. Podrías sumar todas las áreas, filtrar formas por tamaño o transformar la colección. La idea clave es que una vez que los elementos están en una colección de objetos de rasgo (trait objects), interactúas con ellos puramente a través de la interfaz del rasgo, independientemente de sus tipos subyacentes.

challenge icon

Desafío

Fácil

¡Vamos a construir una calculadora de precios para un carrito de compras que puede contener diferentes tipos de artículos! Crearás un sistema donde los productos y servicios —cada uno con una lógica de precios diferente— puedan almacenarse juntos y procesarse para calcular un precio total.

Organizarás tu código en dos archivos:

  • items.rs: Define un trait público Priceable con un método price que tome &self y devuelva un f64. Luego crea dos structs públicos:
    • Product — con campos públicos name (String) y cost (f64). Su price simplemente debe devolver el cost.
    • Service — con campos públicos name (String), hourly_rate (f64) y hours (f64). Su price debe devolver hourly_rate * hours.
  • main.rs: Importa tu módulo de artículos y crea un vector de tipo Vec<Box<dyn Priceable>>. Usa las entradas para crear un Product y un Service, añádelos a tu vector, luego itera a través de la colección para calcular e imprimir el precio total de todos los artículos.

El poder de los objetos de trait brilla aquí: tu código de iteración no necesita saber si está procesando un Product o un Service. Simplemente llama a price() en cada artículo, y Rust despacha la implementación correcta automáticamente.

Tu salida debe mostrar el precio total con un decimal:

Total: ${total}

Por ejemplo, con las entradas Laptop, 999.99, Consulting, 150.0 y 3.0:

Total: $1449.99

Y con las entradas Book, 29.99, Tutoring, 50.0 y 2.0:

Total: $129.99

Recibirás cinco entradas: el nombre del producto, el costo del producto, el nombre del servicio, la tarifa por hora y las horas trabajadas (analiza los valores numéricos como f64).

Hoja de referencia

Para iterar sobre una colección de objetos trait, utiliza un bucle for estándar con una referencia al vector:

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

Al iterar con for item in &vec, cada item es una referencia a un Box<dyn Trait>. Puedes llamar a cualquier método del trait directamente; Rust maneja el despacho dinámico automáticamente, llamando a la implementación correcta para cada tipo concreto.

Ejemplo de iteración 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 patrón es útil para agregar resultados: puedes sumar valores, filtrar por criterios o transformar la colección mientras interactúas con los elementos puramente a través de la interfaz del trait.

Pruébalo tú mismo

mod items;

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

fn main() {
    // Leer 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: Crear un Product usando product_name y product_cost

    // TODO: Crear un Service usando service_name, hourly_rate y hours

    // TODO: Crear un Vec<Box<dyn Priceable>> y añadir ambos elementos a él

    // TODO: Iterar a través del vector y calcular el precio total

    // TODO: Imprimir el total con un decimal
    // Formato: Total: ${total:.1}
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming