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

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

Introducción a los Generics en Rust



La programación genérica (generics) es uno de los conceptos clave de Rust, que permite escribir código flexible, reutilizable y seguro. Si estás migrando a Rust desde otros lenguajes (como C++ o Java), seguramente te has encontrado con plantillas o genéricos. En Rust, los generics funcionan de manera similar, pero con un enfoque especial en el rendimiento y la seguridad de la memoria.



¿Para qué sirven los generics? Imagina que necesitas escribir una función que encuentre el elemento máximo en una lista. Sin generics, tendrías que escribir una versión separada para i32, f64, String y cada tipo personalizado. Con generics, escribes una sola función que funciona con cualquier tipo que admita comparación.



En este artículo, analizaremos qué son los generics en Rust, cómo usarlos con funciones, estructuras y traits, y veremos ejemplos prácticos.



Fundamentos de Generics: funciones y estructuras



Funciones genéricas



La forma más sencilla de familiarizarse con los generics es escribir una función genérica. La sintaxis utiliza corchetes angulares <T>, donde T es un parámetro de tipo (type parameter).



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

fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("El número más grande: {}", result);

let chars = vec!['y', 'm', 'a', 'q']; let result = largest(&chars); println!("El carácter más grande: {}", result);}


¡Sin embargo, este código no se compilará! Rust requiere que el tipo T implemente el trait PartialOrd (para comparación). Las restricciones (trait bounds) las veremos más adelante, pero por ahora recuerda: los generics permiten abstraerse del tipo concreto, pero el compilador debe saber qué operaciones están permitidas.



Estructuras genéricas



Los generics no solo funcionan con funciones, sino también con tipos de datos personalizados. Por ejemplo, un punto con coordenadas puede almacenar números enteros o números de punto flotante:



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

fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; println!("Punto entero: ({}, {})", integer_point.x, integer_point.y); println!("Punto flotante: ({}, {})", float_point.x, float_point.y);}


Se pueden usar múltiples parámetros de tipo si los campos tienen tipos diferentes:



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

fn main() { let pair = Pair { first: 42, second: "respuesta" }; println!("Par: {} y {}", pair.first, pair.second);}


Generics con traits y restricciones (Trait Bounds)



¿Qué son los trait bounds?



Los trait bounds son una forma de indicarle al compilador que un tipo genérico debe implementar ciertos traits. Sin ellos, no podemos llamar métodos sobre un valor genérico. Volvamos a la función largest y corrijámosla:



use std::cmp::PartialOrd;

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

fn main() { let numbers = vec![34, 50, 25, 100, 65]; println!("El más grande: {}", largest(&numbers));}


Ahora la función solo acepta tipos que admiten comparación (PartialOrd).



Restricciones múltiples



Puedes especificar varios traits usando +. Por ejemplo, el tipo debe ser tanto comparable como clonable:



use std::fmt::Display;

fn print_and_compare

Blogs

Book Recommendations