Estructuras genéricas
Parte de la sección Programación Orientada a Objetos del Journey de Rust de Coddy. Lección 24 de 61.
Hasta ahora, todas las estructuras que has creado han tenido campos con tipos específicos y concretos. Un Pet tiene un nombre String, un Rectangle tiene dimensiones f64. Pero ¿qué ocurre si quieres crear una estructura que pueda contener cualquier tipo de datos?
Aquí es donde entran en juego los genéricos. Los genéricos te permiten escribir código flexible y reutilizable que funciona con múltiples tipos. En lugar de codificar directamente un tipo específico, utilizas un marcador de posición, normalmente T, que se reemplaza por un tipo concreto cuando utilizas la estructura.
Aquí tienes una estructura contenedora sencilla que puede contener cualquier tipo:
struct Wrapper<T> {
value: T,
}
La <T> después del nombre de la estructura declara un parámetro de tipo genérico. Dentro de la estructura, T actúa como sustituto del tipo que realmente usarás. Cuando creas una instancia, Rust infiere el tipo concreto:
let int_wrapper = Wrapper { value: 42 }; // T es i32
let float_wrapper = Wrapper { value: 3.14 }; // T es f64
let text_wrapper = Wrapper { value: "hello" }; // T es &str
Cada uno de estos es un tipo concreto diferente, Wrapper<i32>, Wrapper<f64> y Wrapper<&str>, pero todos comparten la misma definición de estructura. Esto elimina la necesidad de escribir estructuras IntWrapper, FloatWrapper y StringWrapper separadas que esencialmente hacen lo mismo.
La letra T es solo una convención (abreviatura de «Tipo»). Podrías usar cualquier identificador válido, pero T es estándar en Rust y en la mayoría de los demás lenguajes con genéricos.
Desafío
Fácil¡Construyamos un contenedor genérico que pueda contener cualquier tipo de valor! Crearás una estructura Container que demuestra cómo los genéricos hacen que tu código sea flexible y reutilizable.
Organizarás tu código en dos archivos:
container.rs: Define una estructura genérica pública llamadaContainer<T>con un único campo públicoitemde tipoT. Este contenedor debe poder contener cualquier tipo: enteros, números de coma flotante, cadenas o cualquier otra cosa.main.rs: Incluye tu módulo de contenedor y demuestra su flexibilidad creando contenedores que contienen diferentes tipos de datos. Crearás tres contenedores y mostrarás lo que contiene cada uno.
En tu archivo principal, crea los siguientes contenedores:
- Un contenedor que contenga un valor entero (la primera entrada, analizada como
i32) - Un contenedor que contenga un valor de coma flotante (la segunda entrada, analizada como
f64) - Un contenedor que contenga una cadena (la tercera entrada, conservada como
String)
Tu salida debe mostrar el contenido de cada contenedor con este formato:
Integer container: {value}
Float container: {value}
String container: {value}Por ejemplo, con las entradas 42, 3.14 y hello:
Integer container: 42
Float container: 3.14
String container: helloObserva cómo la misma definición de Container<T> funciona para los tres tipos diferentes: ¡ese es el poder de los genéricos!
Recibirás tres entradas: un valor entero, un valor de coma flotante y un valor de cadena.
Pruébalo tú mismo
mod container;
use container::Container;
fn main() {
// Lee las tres entradas
let mut input1 = String::new();
std::io::stdin().read_line(&mut input1).expect("Failed to read line");
let int_value: i32 = input1.trim().parse().expect("Failed to parse integer");
let mut input2 = String::new();
std::io::stdin().read_line(&mut input2).expect("Failed to read line");
let float_value: f64 = input2.trim().parse().expect("Failed to parse float");
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: Create three containers using Container<T>
// 1. An integer container holding int_value
// 2. A float container holding float_value
// 3. A string container holding string_value
// TODO: Imprime el contenido de cada contenedor en el formato requerido
// Integer container: {value}
// Float container: {value}
// String container: {value}
}
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 multimediaPractica por tu cuenta: Compilador de Rust online