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

Introducción: ¿qué son los Generics y para qué sirven en Rust?

\n

Imagina que necesitas escribir una función que encuentre el elemento más grande en una lista. Para números enteros — una función, para números de punto flotante — otra, para cadenas — una tercera. El código se duplicaría y sería difícil de mantener. En Rust, este problema se resuelve con Generics (programación genérica).

\n

Los Generics permiten escribir código que funciona con diferentes tipos de datos, sin perder el tipado estricto. En lugar de duplicar funciones para cada tipo, escribes una versión genérica. Rust, en tiempo de compilación, sustituye el tipo concreto y verifica la corrección. Esto proporciona:

\n
    \n
  • Reutilización de código — una función para todos los tipos.
  • \n
  • Seguridad — todos los errores de tipo se detectan en tiempo de compilación.
  • \n
  • Rendimiento — gracias a la monomorfización (el compilador genera código separado para cada tipo utilizado) el código genérico funciona tan rápido como el especializado.
  • \n
\n

En este artículo, analizaremos cómo usar Generics en Rust con ejemplos prácticos, comenzando con funciones simples y terminando con estructuras y traits.

\n\n

1. Generics en funciones: el primer paso hacia la universalidad

\n

Sintaxis y ejemplo básico

\n

Para hacer una función genérica, debes especificar el parámetro de tipo entre corchetes angulares <T>. Generalmente se usan letras mayúsculas: T, U, V. Así es como se ve una función que devuelve el valor pasado:

\n
fn identity<T>(value: T) -> T {\n    value\n}\n\nfn main() {\n    let num = identity(42);\n    let text = identity("Hello");\n    println!("{} {}", num, text); // Imprime: 42 Hello\n}
\n

El compilador deduce el tipo automáticamente según el argumento pasado. También puedes especificar el tipo explícitamente: identity::<i32>(42).

\n\n

Restricciones: traits como límites de tipo

\n

¿Qué pasa si queremos comparar valores? Simplemente escribir value1 > value2 no funcionará — no todos los tipos admiten la comparación. Es necesario indicar que el tipo debe implementar el trait PartialOrd:

\n
fn largest<T: PartialOrd>(list: &[T]) -> &T {\n    let mut largest = &list[0];\n    for item in list {\n        if item > largest {\n            largest = item;\n        }\n    }\n    largest\n}\n\nfn main() {\n    let numbers = vec![34, 50, 25, 100, 65];\n    println!("El número más grande: {}", largest(&numbers));\n\n    let chars = vec!['y', 'm', 'a', 'q'];\n    println!("El carácter más grande: {}", largest(&chars));\n}
\n

Aquí, T: PartialOrd es una restricción de trait. Garantiza que el operador de comparación esté definido para el tipo T. Sin esta restricción, el código no se compilaría.

\n\n

2. Generics en estructuras y enumeraciones

\n

Estructuras con campos genéricos

\n

Los Generics también funcionan muy bien con tipos personalizados. Por ejemplo, la estructura Point puede almacenar coordenadas de cualquier tipo:

\n
struct Point<T> {\n    x: T,\n    y: T,\n}\n\nfn main() {\n    let integer_point = Point { x: 5, y: 10 };\n    let float_point = Point { x: 1.0, y: 4.0 };\n    // let mixed = Point { x: 5, y: 4.0 }; // ¡Error! Ambos campos deben ser del mismo tipo\n}
\n

Si necesitas que los campos puedan ser de diferentes tipos, usa varios parámetros:

\n
struct Point<T, U> {\n    x: T,\n    y: U,\n}\n\nfn main() {\n    let mixed = Point { x: 5, y: 4.0 }; // Ahora funciona\n}
\n\n

Enumeraciones con Generics

\n

La enumeración estándar Option<T> es un excelente ejemplo de una enumeración genérica. Puedes crear la tuya propia:

\n
enum Result<T, E> {\n    Ok(T),\n    Err(E),\n}\n\n// Uso\nfn divide(a: f64, b: f64) -> Result<f64, String> {\n    if b                

Blogs

Book Recommendations