Genéricos en Go: cómo la programación genérica cambia las reglas del juego

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

Todo comenzó con Go 1.18: ¿por qué los genéricos no son solo una característica de moda?

\n\n

Durante mucho tiempo, la comunidad de Go se enorgulleció de la simplicidad del lenguaje. La ausencia de programación genérica (generics) se consideró una elección consciente: "no necesitamos plantillas complejas, escribimos código comprensible". Pero la realidad se impuso. Los desarrolladores se cansaron de copiar y pegar funciones para int, string y float64 — o de usar interface{} con conversiones de tipos posteriores, perdiendo seguridad en tiempo de compilación.

\n\n

El lanzamiento de Go 1.18 (2022) fue una revolución. Los genéricos (generics) agregaron al lenguaje parámetros de tipo (type parameters) y restricciones (constraints). Ahora los programadores de Go pueden escribir estructuras de datos y algoritmos genéricos sin sacrificar rendimiento ni tipificación estricta.

\n\n

En este artículo en pythonlib.ru analizaremos cómo funcionan los generics en Go, veremos ejemplos reales de código y entenderemos cuándo usarlos y cuándo no. ¡Allá vamos!

\n\n

¿Qué son los generics en Go en palabras sencillas?

\n\n

Imagina que escribes una función que encuentra el elemento mínimo en un slice. Sin genéricos, tendrías que crear tres versiones: para int, float64 y string. O usar interface{} con conversión de tipos — lento e inseguro.

\n\n

Los generics permiten escribir una sola función que funciona con cualquier tipo que cumpla ciertas condiciones. La sintaxis es simple: después del nombre de la función, entre corchetes, se indican los parámetros de tipo.

\n\n

Sintaxis: corchetes y la palabra any

\n\n
package main\n\nimport "fmt"\n\n// Min — función genérica para encontrar el mínimo\nfunc Min[T any](a, b T) T {\n    // Error: el operador < no está definido para any\n    // if a < b {\n    //     return a\n    // }\n    return b\n}
\n\n

El código anterior no compilará porque any es una interfaz vacía (sinónimo de interface{}). El compilador no sabe si el tipo soporta el operador <. Es necesario agregar una restricción (constraint).

\n\n

Restricciones de tipos: constraints

\n\n

En Go, las restricciones se definen mediante interfaces. Se pueden usar las integradas del paquete golang.org/x/exp/constraints o escribir las propias.

\n\n
import (\n    "fmt"\n    "golang.org/x/exp/constraints"\n)\n\n// Min funciona con cualquier tipo ordenado\nfunc Min[T constraints.Ordered](a, b T) T {\n    if a < b {\n        return a\n    }\n    return b\n}\n\nfunc main() {\n    fmt.Println(Min(10, 20))       // 10\n    fmt.Println(Min(3.14, 2.71))   // 2.71\n    fmt.Println(Min("apple", "banana")) // "apple"\n}
\n\n

Observa: constraints.Ordered incluye todos los tipos para los que están definidos los operadores <, <=, >, >= — estos son int, float, string.

\n\n

Ejemplos prácticos: de lo simple a lo complejo

\n\n

Veamos cómo los generics en Go simplifican la escritura de código real.

\n\n

1. Pila genérica (Stack)

\n\n

Sin genéricos, habría que almacenar []interface{} y hacer type assertion. Con generics — elegante y seguro.

\n\n
package main\n\nimport "fmt"\n\n// Stack — estructura de datos genérica\ntype Stack[T any] struct {\n    items []T\n}\n\nfunc (s *Stack[T]) Push(item T) {\n    s.items = append(s.items, item)\n}\n\nfunc (s *Stack[T]) Pop() (T, bool) {\n    if len(s.items) == 0 {\n        var zero T\n        return zero, false\n    }\n    item := s.items[len(s.items)-1]\n    s.items = s.items[:len(s.items)-1]\n    return item, true\n}\n\nfunc main() {\n    intStack := Stack[int]{}\n    intStack.Push(1)\n    intStack.Push(2)\n    val, ok := intStack.Pop()\n    fmt.Println(val, ok) // 2 true\n\n    strStack := Stack[string]{}\n    strStack.Push("hello")\n                

Blogs

Book Recommendations