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 distintos tipos en un Vec<Box<dyn Trait>>, el siguiente paso es procesar esa collection. Iterar sobre objetos de traits funciona igual que iterar sobre cualquier vector. Recorres cada elemento y llamas a los métodos definidos por el 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());
}
}
Cuando iteras con for shape in &shapes, cada shape es una referencia a un Box<dyn Shape>. Puedes llamar directamente a cualquier método del trait Shape: Rust gestiona automáticamente el despacho dinámico y llama a la implementación correcta para cada tipo concreto.
Este patrón es potente para agregar resultados. Puedes sumar todas las áreas, filtrar las 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 trait, interactúas con ellos únicamente a través de la interfaz del trait, independientemente de sus tipos subyacentes.
Desafío
Fácil¡Construyamos una calculadora de precios para un carrito de compras que pueda contener distintos tipos de elementos! Crearás un sistema en el que los productos y los 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úblicoPriceablecon unmethodpriceque recibe&selfy returns unf64. Después, crea dos structs públicos:Product: con camposname(String) ycost(f64) públicos. Supriceshould simplemente devolver elcost.Service: con camposname(String),hourly_rate(f64) yhours(f64) públicos. Supriceshould devolverhourly_rate * hours.
main.rs: Incluye tu módulo de items y crea un vector de tipoVec<Box<dyn Priceable>>. Usa las entradas para crear un Product y un Service, add ambos al vector y después recorre la collection para calcular e imprimir el precio total de todos los items.
Aquí es donde brilla el poder de los objetos de traits. Tu código de iteración no necesita saber si está procesando un Product o un Service. Simplemente llama a price() en cada item, y Rust selecciona automáticamente la implementación correcta.
Tu salida should mostrar el precio total con dos decimales:
Total: ${total}Por ejemplo, con las entradas Laptop, 999.99, Consulting, 150.0 y 3.0:
Total: $1449.99Y con las entradas Book, 29.99, Tutoring, 50.0 y 2.0:
Total: $129.99Recibirás cinco entradas: el nombre del producto, el coste del producto, el nombre del servicio, la tarifa por hora y las horas trabajadas (analiza los valores numéricos como f64).
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: Crea un Product usando product_name y product_cost
// TODO: Crea un Service usando service_name, hourly_rate y hours
// TODO: Crea un Vec<Box<dyn Priceable>> y añade ambos elementos a él
// TODO: Itera a través del vector y calcula el precio total
// TODO: Imprime el total con un decimal
// Formato: Total: ${total:.1}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Métodos y Comportamiento
Intro a Bloques de ImplementaciónEl Parámetro SelfMétodos MutablesFunciones AsociadasMúltiples Bloques de ImplementaciónEncadenamiento de MétodosResumen - Acciones de Rectángulo4Proyecto: Mascota virtual
Definir la mascotaAlimentar a la mascota7Traits estándar
El trait DebugEl trait DisplayClone y CopyTraits de igualdadResumen - Punto imprimible10Proyecto: Sistema de documentos
El Trait DrawComponente de texto2Encapsulamiento y módulos
Conceptos básicos de módulosLa palabra clave PublicCampos privadosGettersSettersRepaso - Casillero seguro5Genéricos
Estructuras genéricasMétodos genéricosMúltiples tipos genéricosFunciones genéricasResumen - Punto de coordenadas8Traits como límites
Sintaxis de límites de traitsLímites múltiplesLa cláusula whereRetornar tipos con traitsResumen: Impresora genérica11Patrones de diseño en Rust
Patrón NewtypeComposiciónEl Trait DropFrom e IntoResumen - Mock de Smart Pointer3Enums avanzados
Enums con datosMétodos en EnumsCoincidencia de variantes de datosEl Enum Option revisitadoResumen - Enum Shape6Definición de Traits
¿Qué es un Trait?Implementación de TraitsImplementaciones por defectoSobrescribir valores por defectoTraits con parámetrosResumen - Reproductor multimedia9Objetos de Trait
Despacho dinámicoVectores de TraitsIteración de objetos de TraitRepaso - Gestor del zoológicoPractica por tu cuenta: Compilador de Rust online