Menu
Coddy logo textTech

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.

challenge icon

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 sea Clone, 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:

  1. Usando wrap_in_pair con un entero (primera entrada, analizada como i32) e imprimiendo ambos elementos
  2. Usando wrap_in_pair con una cadena (segunda entrada) e imprimiendo ambos elementos
  3. Usando swap con un entero (tercera entrada, analizada como i32) 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})
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming