Implementaciones por defecto
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 31 de 61.
Hasta ahora, cada método de trait que hemos definido ha sido solo una firma: un requisito sin cuerpo. ¿Pero qué pasa si la mayoría de los tipos que implementan un trait usaran la misma lógica? Rust te permite proporcionar una implementación predeterminada directamente en la definición del trait.
En lugar de terminar la firma del método con un punto y coma, se añade un cuerpo:
trait Greet {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
Ahora cualquier tipo que implemente Greet obtiene automáticamente este método, sin escribir ningún código en el bloque impl:
struct Guest;
impl Greet for Guest {}
let visitor = Guest;
println!("{}", visitor.greet()); // ¡Hola!
La implementación vacía impl Greet for Guest {} es válida porque el trait ya proporciona el comportamiento. La estructura simplemente se "adhiere" al trait y recibe el método predeterminado de forma gratuita.
Este patrón es útil cuando quieres definir un comportamiento compartido que funcione para la mayoría de los casos. Los tipos que necesiten algo diferente pueden sobrescribir el comportamiento predeterminado (que aprenderás a continuación), mientras que los tipos que estén conformes con el comportamiento estándar no necesitan repetirse.
Desafío
Fácil¡Exploremos el poder de las implementaciones por defecto construyendo un sistema de notificaciones! Crearás un trait que proporcione un comportamiento estándar de fábrica, para que los tipos puedan optar por él sin escribir código repetitivo.
Organizarás tu código en tres archivos:
notifiable.rs: Define un trait públicoNotifiablecon un métodonotify(&self) -> Stringque tenga una implementación por defecto. El valor por defecto debe devolver"You have a new notification!". Cualquier tipo que implemente este trait recibirá automáticamente este comportamiento sin necesidad de escribir nada en su bloque impl.alerts.rs: Crea dos structs de unidad públicos simples que implementen tu trait:EmailAlert— implementaNotifiablecon un bloque impl vacío (usa el valor por defecto)SystemAlert— también implementaNotifiablecon un bloque impl vacío (usa el valor por defecto)
main.rs: Une tus módulos y demuestra cómo ambos tipos de alerta comparten el mismo comportamiento por defecto. Crea instancias de ambos structs y llama a sus métodosnotify.
En tu archivo principal, crea un EmailAlert y un SystemAlert, luego imprime sus notificaciones en líneas separadas.
Tu salida debería ser:
You have a new notification!
You have a new notification!Observa cómo ambos tipos producen una salida idéntica: ¡esa es la belleza de las implementaciones por defecto! Ningún struct necesitó escribir código para el método notify, y sin embargo, ambos tienen un comportamiento de notificación plenamente funcional. En la siguiente lección, aprenderás cómo sobrescribir estos valores por defecto cuando un tipo necesite un comportamiento personalizado.
Hoja de referencia
Los métodos de los traits pueden tener implementaciones por defecto proporcionando un cuerpo de método en lugar de solo una firma:
trait Greet {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
Los tipos que implementan el trait reciben automáticamente el comportamiento por defecto sin escribir ningún código:
struct Guest;
impl Greet for Guest {}
let visitor = Guest;
println!("{}", visitor.greet()); // ¡Hola!
Un bloque impl vacío es válido cuando el trait proporciona implementaciones por defecto. Esto permite que los tipos opten por un comportamiento compartido sin repetición.
Pruébalo tú mismo
mod notifiable;
mod alerts;
use notifiable::Notifiable;
use alerts::{EmailAlert, SystemAlert};
fn main() {
// TODO: Crear una instancia de EmailAlert
// TODO: Crear una instancia de SystemAlert
// TODO: Imprimir la notificación de EmailAlert
// TODO: Imprimir la notificación de SystemAlert
}
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