Vectores de Traits
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 46 de 61.
Has visto cómo Box<dyn Trait> permite que una sola variable contenga diferentes tipos. El verdadero poder surge cuando combinas esto con colecciones: almacenando múltiples tipos diferentes en un solo vector.
Un vector regular requiere que todos los elementos sean del mismo tipo. No puedes tener un Vec<Dog> que también contenga gatos. Pero con los objetos de rasgo (trait objects), puedes crear un vector que contenga cualquier cosa que implemente un rasgo compartido:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
El tipo Vec<Box<dyn Speak>> significa "un vector de objetos de rasgo (trait objects) en cajas (boxed)". Cada elemento es un Box que apunta a algo que implementa Speak—los tipos reales pueden diferir. Envuelves cada valor con Box::new() al añadirlo al vector.
Este patrón es esencial para construir sistemas flexibles donde necesites gestionar colecciones de objetos relacionados pero diferentes, como componentes de interfaz de usuario (UI), entidades de juego o sistemas de complementos (plugins).
Desafío
Fácil¡Vamos a construir un sistema de notificaciones que pueda manejar diferentes tipos de alertas! Crearás una colección que almacene varios tipos de notificaciones—correos electrónicos, mensajes SMS y notificaciones push—todo en un único vector utilizando objetos de rasgo (trait objects).
Organizarás tu código en dos archivos:
notifications.rs: Define un trait públicoNotifycon un métodosendque tome&selfy devuelva unStringdescribiendo la notificación que se está enviando. Luego crea tres structs públicos:Email— con un campo públicorecipient(String). Su métodosenddebe devolver"Email to: {}"Sms— con un campo públicophone(String). Su métodosenddebe devolver"SMS to: {}"Push— con un campo públicodevice(String). Su métodosenddebe devolver"Push to: {}"
main.rs: Importa tu módulo de notificaciones y crea un vector de tipoVec<Box<dyn Notify>>que contenga los tres tipos de notificaciones. Utiliza las entradas proporcionadas para crear una de cada tipo de notificación, añádelas a tu vector en orden (Email, Sms, Push), y luego imprime el resultado de llamar asend()en el primer elemento del vector.
El concepto clave aquí es que, a pesar de que Email, Sms y Push son structs completamente diferentes, todos pueden convivir en el mismo vector porque comparten el trait Notify. Cada elemento se envuelve con Box::new() para crear el objeto de rasgo.
Tu salida debería mostrar la primera notificación:
Email to: {recipient}Por ejemplo, con las entradas "alice@example.com", "555-1234" y "iPhone-12":
Email to: alice@example.comRecibirás tres entradas: el destinatario del correo electrónico, el número de teléfono y el nombre del dispositivo.
Pruébalo tú mismo
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Leer entradas
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: Crear un Vec<Box<dyn Notify>> para contener diferentes tipos de notificación
// TODO: Crear instancias de Email, Sms y Push usando las entradas
// TODO: Añadirlas al vector en orden (Email, Sms, Push) usando Box::new()
// TODO: Imprimir el resultado de llamar a send() en el primer elemento
}
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