Todo comenzó con Go 1.18: ¿por qué los genéricos no son solo una característica de moda?
\n\nDurante 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.
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\nEn 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\nImagina 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.
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\nSintaxis: corchetes y la palabra any
\n\npackage 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\nEl 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).
Restricciones de tipos: constraints
\n\nEn Go, las restricciones se definen mediante interfaces. Se pueden usar las integradas del paquete golang.org/x/exp/constraints o escribir las propias.
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\nObserva: constraints.Ordered incluye todos los tipos para los que están definidos los operadores <, <=, >, >= — estos son int, float, string.
Ejemplos prácticos: de lo simple a lo complejo
\n\nVeamos cómo los generics en Go simplifican la escritura de código real.
\n\n1. Pila genérica (Stack)
\n\nSin genéricos, habría que almacenar []interface{} y hacer type assertion. Con generics — elegante y seguro.
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