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 (trait objects) 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.
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úblicoPriceablecon un métodopriceque tome&selfy devuelva unf64. Luego crea dos structs públicos:Product— con campos públicosname(String) ycost(f64). Supricesimplemente debe devolver elcost.Service— con campos públicosname(String),hourly_rate(f64), yhours(f64). Supricedebe devolverhourly_rate * hours.
main.rs: Importa tu módulo de artículos y crea un vector de tipoVec<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 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 costo 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: 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}
}
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