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.
Hoja de referencia
Un Vec<Box<dyn Trait>> permite almacenar múltiples tipos diferentes en un solo vector, siempre que todos implementen el mismo trait.
Los vectores regulares requieren que todos los elementos sean del mismo tipo, pero los objetos de trait permiten colecciones heterogéneas:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
];
}
Cada elemento se envuelve con Box::new() al añadirlo al vector. Los tipos reales pueden diferir siempre que implementen el trait compartido.
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