Vectores de Traits
Parte de la sección Programación Orientada a Objetos 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. La verdadera potencia surge cuando combinas esto con colecciones: almacenar varios tipos diferentes en un solo vector.
Un vector normal requiere que todos los elementos sean del mismo tipo. No puedes tener un Vec<Dog> que también contenga gatos. Pero con objetos de rasgo, 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 encapsulados». Cada elemento es un Box que apunta a algo que implementa Speak: los tipos reales pueden ser diferentes. Envuelves cada valor con Box::new() al añadirlo al vector.
Este patrón es esencial para crear sistemas flexibles en los que necesitas gestionar colecciones de objetos relacionados pero diferentes, como componentes de UI, entidades de juegos o sistemas de plugins.
Desafío
Fácil¡Construyamos un sistema de notificaciones que pueda gestionar distintos tipos de alertas! Crearás una colección que almacena varios tipos de notificaciones, correos electrónicos, mensajes SMS y notificaciones push, todo en un único vector mediante objetos de trait.
Organizarás tu código en dos archivos:
notifications.rs: Define un trait públicoNotifycon un métodosendque recibe&selfy devuelve unStringque describe la notificación que se está enviando. Después, crea tres structs públicos:Email: con un campo públicorecipient(String). Su métodosenddebe devolverEmail to: {recipient}Sms: con un campo públicophone(String). Su métodosenddebe devolverSMS to: {phone}Push: con un campo públicodevice(String). Su métodosenddebe devolverPush to: {device}
main.rs: Incorpora tu módulo de notificaciones y crea un vector de tipoVec<Box<dyn Notify>>que contenga los tres tipos de notificación. Usa las entradas proporcionadas para crear una notificación de cada tipo, añádelas a tu vector en orden (Email, Sms, Push) y después imprime el resultado de llamar asend()en el primer elemento del vector.
El concepto clave aquí es que, aunque Email, Sms y Push sean 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 trait.
Tu salida debe 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: Crea un Vec<Box<dyn Notify>> para contener diferentes tipos de notificación
// TODO: Crea instancias de Email, Sms y Push usando las entradas
// TODO: Añádelos al vector en orden (Email, Sms, Push) usando Box::new()
// TODO: Imprime 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 Programación Orientada a Objetos
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