Métodos genéricos
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 25 de 61.
Una estructura genérica puede contener cualquier tipo, pero no es muy útil sin métodos para interactuar con sus datos. Para definir métodos para una estructura genérica, necesitas una sintaxis especial en el bloque impl.
La clave es declarar el parámetro genérico en el propio impl:
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn get(&self) -> &T {
&self.value
}
}
Observa el impl<T> antes de Wrapper<T>. Esto le indica a Rust que T es un parámetro de tipo genérico para todo el bloque de implementación. Sin esta declaración, Rust buscaría un tipo concreto llamado T y no lograría encontrarlo.
El método get devuelve &T—una referencia a cualquier tipo que contenga el envoltorio. Esto funciona independientemente de si T es un entero, una cadena o cualquier otro tipo:
let num_wrapper = Wrapper { value: 100 };
let text_wrapper = Wrapper { value: "Rust" };
println!("{}", num_wrapper.get()); // 100
println!("{}", text_wrapper.get()); // Rust
La misma definición de método funciona para ambos, porque el genérico T se adapta a cada tipo concreto en tiempo de compilación.
Desafío
Fácil¡Vamos a extender tu contenedor genérico con métodos! Construirás una estructura Box (no confundir con el Box estándar de Rust) que puede contener cualquier tipo y proporciona métodos para interactuar con su contenido.
Organizarás tu código en dos archivos:
mybox.rs: Define una estructura genérica pública llamadaMyBox<T>con un campo privadocontentsde tipoT. Implementa métodos para esta estructura:- Una función asociada
newque crea un nuevoMyBoxcon el valor dado - Un método
peekque devuelve una referencia al contenido (usando&self) - Un método
replaceque toma un nuevo valor y reemplaza el contenido actual (usando&mut self)
- Una función asociada
main.rs: Importa tu módulo y demuestra los métodos genéricos funcionando con diferentes tipos. Crearás cajas, observarás su contenido y reemplazarás valores para mostrar los métodos en acción.
Recuerda la sintaxis clave para implementar métodos en una estructura genérica: necesitas impl<T> antes de MyBox<T> para indicarle a Rust que T es un parámetro genérico para todo el bloque de implementación.
En tu archivo principal, demuestra tu MyBox de la siguiente manera:
- Creando una caja con un entero (primera entrada, analizada como
i32) - Observando su contenido e imprimiendo el valor
- Reemplazando el contenido con un nuevo entero (segunda entrada, analizada como
i32) - Observando de nuevo para mostrar el valor actualizado
- Creando una segunda caja con una cadena (tercera entrada)
- Observando el contenido de la caja de cadena
Tu salida debe seguir este formato:
Integer box contains: {value}
After replace: {value}
String box contains: {value}Por ejemplo, con las entradas 10, 25 y Rust:
Integer box contains: 10
After replace: 25
String box contains: RustRecibirás tres entradas: un entero inicial, un entero de reemplazo y un valor de cadena.
Hoja de referencia
Para definir métodos para una estructura genérica, declare el parámetro genérico en el bloque impl:
struct Wrapper<T> {
value: T,
}
impl<T> Wrapper<T> {
fn get(&self) -> &T {
&self.value
}
}
La sintaxis impl<T> le indica a Rust que T es un parámetro de tipo genérico para todo el bloque de implementación. Sin esto, Rust buscaría un tipo concreto llamado T.
Los métodos pueden devolver referencias al tipo genérico (&T), lo cual funciona para cualquier tipo concreto:
let num_wrapper = Wrapper { value: 100 };
let text_wrapper = Wrapper { value: "Rust" };
println!("{}", num_wrapper.get()); // 100
println!("{}", text_wrapper.get()); // Rust
Pruébalo tú mismo
mod mybox;
use mybox::MyBox;
fn main() {
// Leer entradas
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let initial_int: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let replacement_int: i32 = input2.trim().parse().expect("Invalid integer");
let mut input3 = String::new();
std::io::stdin().read_line(&mut input3).expect("Failed to read line");
let string_value = input3.trim().to_string();
// TODO: Crear un MyBox con el entero inicial
// TODO: Observar su contenido e imprimir: "Integer box contains: {value}"
// TODO: Reemplazar el contenido con el entero de reemplazo
// TODO: Observar de nuevo e imprimir: "After replace: {value}"
// TODO: Crear un segundo MyBox con el valor de cadena
// TODO: Observar la caja de cadena e imprimir: "String box contains: {value}"
}
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