Estructuras genéricas
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 24 de 61.
Hasta ahora, cada struct que has creado ha tenido campos con tipos específicos y concretos. Un Pet tiene un nombre String, un Rectangle tiene dimensiones f64. ¿Pero qué pasa si quieres crear un struct 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 de forma rígida un tipo específico, utilizas un marcador de posición —normalmente T— que se sustituye por un tipo concreto cuando utilizas la struct.
Aquí tienes una estructura wrapper simple que puede contener cualquier tipo:
struct Wrapper<T> {
value: T,
}
El <T> después del nombre de la estructura declara un parámetro de tipo genérico. Dentro de la estructura, T actúa como un sustituto para cualquier tipo que realmente utilices. 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 struct. Esto elimina la necesidad de escribir structs IntWrapper, FloatWrapper y StringWrapper por separado que hacen esencialmente lo mismo.
La letra T es solo una convención (abreviatura de "Type"). Podrías usar cualquier identificador válido, pero T es el estándar en Rust y en la mayoría de los demás lenguajes con genéricos.
Desafío
Fácil¡Vamos a construir un contenedor genérico que pueda albergar 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 ser capaz de albergar cualquier tipo: enteros, flotantes, cadenas o cualquier otra cosa.main.rs: Importa tu módulo de contenedor y demuestra su flexibilidad creando contenedores que alberguen 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 albergue un valor entero (la primera entrada, analizada como
i32) - Un contenedor que albergue un valor flotante (la segunda entrada, analizada como
f64) - Un contenedor que albergue una cadena (la tercera entrada, mantenida como un
String)
Tu salida debe mostrar el contenido de cada contenedor en 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 flotante y un valor de cadena.
Hoja de referencia
Los genéricos te permiten crear estructuras que pueden contener cualquier tipo de datos utilizando un marcador de posición de parámetro de tipo.
Para declarar una estructura genérica, utiliza corchetes angulares con un parámetro de tipo (convencionalmente T):
struct Wrapper<T> {
value: T,
}
El <T> declara un parámetro de tipo genérico, y T dentro de la estructura actúa como un marcador de posición para el tipo real.
Al crear instancias, 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 instancia se convierte en un tipo concreto diferente (Wrapper<i32>, Wrapper<f64>, Wrapper<&str>) mientras comparten la misma definición de estructura.
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. Un contenedor de enteros que contenga int_value
// 2. Un contenedor de flotantes que contenga float_value
// 3. Un contenedor de cadenas que contenga string_value
// TODO: Print cada contenido del 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 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