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.
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 un decimal:
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).
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}
}
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ógico