Sintaxis de límites de traits
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 40 de 61.
Hasta ahora, has aprendido a definir traits e implementarlos para structs. También has trabajado con genéricos para escribir código flexible. Ahora es el momento de combinar estos conceptos: usar traits para restringir qué tipos puede aceptar una función genérica.
Considera una función genérica que necesita llamar a un método específico en su parámetro. Sin ninguna restricción, Rust no tiene garantía de que el tipo T tenga ese método:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // ¡Error! Es posible que T no tenga summarize()
}
La solución es una restricción de trait (trait bound). Al añadir : TraitName después del parámetro genérico, le indicas a Rust que T debe implementar ese trait:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Ahora Rust sabe que este método existe
}
La sintaxis <T: Summary> se lee como "T es cualquier tipo que implementa Summary". Ahora la función solo acepta tipos que tienen el comportamiento requerido. Si intentas pasar un tipo que no implementa Summary, el compilador lo rechazará con un error claro.
Este patrón es potente porque te permite escribir código genérico que sigue teniendo acceso a una funcionalidad específica. Tu función sigue siendo flexible (funciona con cualquier tipo), pero solo con tipos que proporcionan el comportamiento que necesitas.
Desafío
Fácil¡Vamos a construir un sistema de notificaciones que utiliza límites de rasgos (trait bounds) para asegurar que solo se puedan enviar mensajes con el formato adecuado! Crearás una función genérica que acepte cualquier tipo que implemente un rasgo específico, demostrando cómo los límites de rasgos restringen los parámetros genéricos.
Organizarás tu código en dos archivos:
notification.rs: Define un rasgo públicoNotifycon un método llamadomessageque tome&selfy devuelva unString. Luego, crea dos estructuras públicas que implementen este rasgo:Email— con un campo públicosubject(String). Su métodomessagedebe devolverEmail: {subject}SMS— con un campo públicocontent(String). Su métodomessagedebe devolverSMS: {content}
send_notificationque acepte cualquier tipoTque implemente el rasgoNotify. Esta función debe imprimir el resultado de llamar amessage()en el elemento.main.rs: Incorpora tu módulo de notificación y utiliza las entradas proporcionadas para crear una instancia deEmaily otra deSMS. Llama asend_notificationcon cada una para demostrar que tu función genérica funciona con cualquier tipo que implementeNotify.
La idea clave aquí es que send_notification no conoce el tipo concreto que recibe; solo sabe que el tipo puede producir un mensaje. El límite de rasgo T: Notify garantiza esta capacidad.
Tu salida debería mostrar el envío de ambas notificaciones:
Email: {subject}
SMS: {content}Por ejemplo, con las entradas Meeting Tomorrow y On my way!:
Email: Meeting Tomorrow
SMS: On my way!Recibirás dos entradas: el asunto del correo electrónico (email subject) y el contenido del SMS.
Hoja de referencia
Un trait bound restringe un parámetro de tipo genérico para que solo acepte tipos que implementen un trait específico.
La sintaxis para los trait bounds utiliza : TraitName después del parámetro genérico:
fn function_name<T: TraitName>(item: T) {
// Ahora se pueden llamar métodos de TraitName en item
}
Ejemplo con un trait bound:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize());
}
La notación <T: Summary> significa "T es cualquier tipo que implemente Summary". Esto permite que la función llame a los métodos del trait mientras sigue siendo genérica.
Pruébalo tú mismo
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Leer entradas
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Crear una instancia de Email con el asunto (subject)
// TODO: Crear una instancia de SMS con el contenido (content)
// TODO: Llamar a send_notification con el email
// TODO: Llamar a send_notification con el sms
}
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 multimedia