Generics en Rust: para qué sirven y cómo usarlos para principiantes

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Generics en Rust: para qué sirven y cómo usarlos

Los Generics (genéricos) son uno de los conceptos clave de Rust, que permiten escribir código flexible, reutilizable y seguro. Si estás empezando a aprender Rust, comprender los genéricos te abrirá el camino para escribir bibliotecas y aplicaciones eficientes. En este artículo, analizaremos qué son los Generics, para qué sirven y cómo aplicarlos en la práctica.



¿Qué son los Generics y para qué sirven?

Imagina que necesitas escribir una función que encuentre el elemento máximo en un array. Para números sería una implementación, para cadenas otra. Para no copiar el código para cada tipo, Rust ofrece programación genérica mediante Generics.

Los genéricos permiten definir estructuras, funciones, enumeraciones o métodos que funcionan con un tipo de dato arbitrario, sin vincularse a uno específico. El compilador, durante la compilación, sustituye el tipo real — este proceso se llama monomorfización.

Sin genéricos, escribirías:

fn max_i32(list: &[i32]) -> i32 {    let mut max = list[0];    for &item in list {        if item > max {            max = item;        }    }    max}

fn max_f64(list: &[f64]) -> f64 { let mut max = list[0]; for &item in list { if item > max { max = item; } } max}

Con genéricos, esto se convierte en una única función universal.



Fundamentos del uso de Generics

Funciones genéricas

Para hacer una función genérica, debes especificar un parámetro de tipo entre corchetes angulares <T>:

fn max<T>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Pero este código no compilará — el compilador dirá que para el tipo T no está implementado el trait PartialOrd. Aquí es donde entran en juego las restricciones de traits.



Estructuras y enumeraciones genéricas

Las estructuras también pueden ser genéricas:

struct Point<T> {    x: T,    y: T,}

fn main() { let int_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.5, y: 3.8 };}

Se pueden especificar varios parámetros de tipo:

struct Pair<T, U> {    first: T,    second: U,}

Las enumeraciones también admiten genéricos — recuerda Option<T> o Result<T, E> de la biblioteca estándar.



Métodos genéricos

Para estructuras con genéricos se pueden definir métodos:

impl<T> Point<T> {    fn x(&self) -> &T {        &self.x    }}

O solo para un tipo específico:

impl Point<f64> {    fn distance_from_origin(&self) -> f64 {        (self.x.powi(2) + self.y.powi(2)).sqrt()    }}


Restricciones de traits (Trait Bounds)

Para usar operaciones de comparación, suma u otros métodos, debes indicar qué traits implementa el tipo. Volvamos a la función max:

fn max<T: PartialOrd>(list: &[T]) -> T {    let mut max = &list[0];    for item in list {        if item > max {            max = item;        }    }    *max}

Ahora el código compila — hemos dicho que T debe soportar ordenamiento parcial (PartialOrd).

Se pueden combinar traits con +:

fn clone_and_compare<T: Clone + PartialOrd>(a: T, b: T) -> bool {    a.clone() > b.clone()}

O usar la sintaxis where para mayor legibilidad:

fn some_function<T, U>(t: &T, u: &U) -> i32where    T:                

Blogs

Book Recommendations