Funciones genéricas
Parte de la sección Object Oriented Programming del Journey de Rust de Coddy — lección 27 de 61.
Los genéricos no se limitan a los structs; también puedes escribir funciones independientes que funcionen con cualquier tipo. Esto es útil cuando necesitas una función de utilidad que no pertenece a un struct específico pero que aun así debe ser flexible.
La sintaxis refleja lo que has visto con las estructuras. Declara el parámetro genérico entre paréntesis angulares después del nombre de la función, luego úsalo en los parámetros y en el tipo de retorno:
fn identity<T>(value: T) -> T {
value
}
Esta función identity toma un valor de cualquier tipo y lo devuelve sin cambios. El <T> declara el genérico, value: T lo acepta como un parámetro, y -> T especifica el tipo de retorno. Cuando llamas a la función, Rust infiere el tipo concreto:
let num = identity(42); // T es i32
let text = identity("hello"); // T es &str
También puedes usar múltiples parámetros genéricos en funciones, al igual que con las estructuras:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // devuelve (i32, &str)
Las funciones genéricas te permiten escribir lógica reutilizable una sola vez y aplicarla a muchos tipos, reduciendo la duplicación de código mientras se mantiene la seguridad de tipos.
Desafío
Fácil¡Vamos a construir un módulo de utilidad con funciones genéricas que pueden trabajar con cualquier tipo! Crearás funciones independientes que demuestran cómo los genéricos hacen que tu código sea flexible y reutilizable sin estar atado a una estructura específica.
Organizarás tu código en dos archivos:
utils.rs: Crea una colección de funciones de utilidad genéricas públicas:wrap_in_pair<T>— toma un solo valor y devuelve una tupla que contiene ese valor dos veces:(value, value). Esto requiere que el tipo seaClone, así que usa<T: Clone>swap<T, U>— toma dos valores de tipos potencialmente diferentes y los devuelve en orden inverso como una tupla(U, T)
main.rs: Importa tu módulo de utilidad y demuestra estas funciones genéricas trabajando con diferentes tipos. Muestra cómo las mismas definiciones de función manejan enteros, flotantes y cadenas de texto sin problemas.
In tu archivo principal, demuestra tus funciones de utilidad:
- Usando
wrap_in_paircon un entero (primera entrada, analizada comoi32) e imprimiendo ambos elementos - Usando
wrap_in_paircon una cadena (segunda entrada) e imprimiendo ambos elementos - Usando
swapcon un entero (tercera entrada, analizada comoi32) y una cadena (cuarta entrada), luego imprimiendo el resultado intercambiado
Tu salida debe seguir este formato:
Pair of ints: ({value}, {value})
Pair of strings: ({value}, {value})
Swapped: ({string}, {int})Por ejemplo, con las entradas 5, hello, 42, y world:
Pair of ints: (5, 5)
Pair of strings: (hello, hello)
Swapped: (world, 42)Observa cómo wrap_in_pair funciona de manera idéntica tanto para enteros como para cadenas, y swap maneja dos tipos completamente diferentes—¡esa es la flexibilidad de las funciones genéricas!
Recibirás cuatro entradas: un entero, una cadena, otro entero y otra cadena.
Hoja de referencia
Las funciones genéricas le permiten escribir funciones de utilidad independientes que funcionan con cualquier tipo. Declare el parámetro genérico entre paréntesis angulares después del nombre de la función:
fn identity<T>(value: T) -> T {
value
}
El <T> declara el parámetro de tipo genérico, que luego puede usarse en los parámetros de la función y en los tipos de retorno. Rust infiere el tipo concreto cuando se llama a la función:
let num = identity(42); // T is i32
let text = identity("hello"); // T is &str
Puede utilizar múltiples parámetros genéricos en una sola función:
fn make_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
let pair = make_pair(10, "ten"); // returns (i32, &str)
Cuando un tipo genérico necesita capacidades específicas, utilice límites de rasgos (trait bounds):
fn duplicate<T: Clone>(value: T) -> (T, T) {
(value.clone(), value)
}
Pruébalo tú mismo
mod utils;
use std::io;
fn main() {
// Leer las cuatro entradas
let mut input1 = String::new();
io::stdin().read_line(&mut input1).expect("Failed to read line");
let num1: i32 = input1.trim().parse().expect("Invalid integer");
let mut input2 = String::new();
io::stdin().read_line(&mut input2).expect("Failed to read line");
let str1 = input2.trim().to_string();
let mut input3 = String::new();
io::stdin().read_line(&mut input3).expect("Failed to read line");
let num2: i32 = input3.trim().parse().expect("Invalid integer");
let mut input4 = String::new();
io::stdin().read_line(&mut input4).expect("Failed to read line");
let str2 = input4.trim().to_string();
// TODO: Usar wrap_in_pair con num1 e imprimir el resultado
// Formato: Pair of ints: ({value}, {value})
// TODO: Usar wrap_in_pair con str1 e imprimir el resultado
// Formato: Pair of strings: ({value}, {value})
// TODO: Usar swap con num2 y str2, luego imprimir el resultado intercambiado
// Formato: Swapped: ({string}, {int})
}
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