Generics en Go: tres años después, qué ha sobrevivido
Actualizado: 2026-05-03
Cuando Go 1.18 añadió generics en marzo de 2022, la comunidad se dividió en dos campos. Unos llevaban años pidiéndolos y los recibieron como el paso que faltaba para modernizar el lenguaje. Otros sospechaban que iban a corromper la simplicidad que hizo a Go atractivo. Ambos tenían parte de razón, y tres años después tenemos perspectiva suficiente para juzgarlo sin la pasión del momento inicial.
Lo más interesante es que el resultado no ha ido exactamente en ninguna de las dos direcciones previstas. Los generics en Go no han explotado en uso masivo, pero tampoco han sido una patata caliente ignorada. Ocupan un sitio muy concreto en el lenguaje, y entender cuál es ese sitio dice bastante sobre cómo ha evolucionado el pensamiento de la comunidad.
Puntos clave
- Los generics no sustituyeron a
interface{}en la superficie del código idiomático — la predicción más común fue incorrecta. - Donde han arraigado con fuerza es en librerías de bajo nivel: colecciones, primitivas de concurrencia, clientes de base de datos.
- La librería
slices(promovida a estándar en Go 1.21) es el caso de uso diario más tangible. - Los métodos genéricos en receptores no genéricos siguen sin permitirse — la comunidad los reemplaza con funciones libres.
- La regla que mejor ha aguantado: escribe la función concreta primero; generaliza solo si copias la misma lógica para tres tipos distintos.
Lo que no ha ocurrido
La predicción más común al principio era que los generics iban a sustituir a interface{} en todos los contextos donde este se usaba. Lo cierto es que eso no ha pasado. Si miras el código idiomático de los proyectos Go más activos — Kubernetes, Docker, CockroachDB, las propias librerías estándar — la superficie pública sigue dominada por interfaces tradicionales, funciones con firmas concretas, y uso de any donde la flexibilidad es realmente necesaria.
La razón principal es que los generics, en la forma que finalmente se aprobó para Go, tienen restricciones de diseño que los hacen menos universales que los templates de C++ o los trait bounds de Rust. No se puede hacer cualquier cosa: las operaciones que se pueden aplicar a un tipo genérico están limitadas por las interfaces de constraint que declaras. Esto es una virtud de diseño, porque mantiene los tiempos de compilación razonables y evita los mensajes de error incomprensibles que C++ produce, pero tiene un coste de expresividad.
La segunda razón es cultural. La filosofía de Go siempre ha preferido la concreción a la abstracción, y la comunidad ha internalizado eso lo suficiente como para que el reflejo por defecto sea “escribe la función concreta primero, generaliza solo si se demuestra necesario”. La existencia de generics no ha cambiado ese reflejo.
Lo que sí ha pasado
Donde los generics han arraigado con fuerza es en las capas profundas de las librerías. Si abres código de golang.org/x/exp/slices, de las utilidades de sync en versiones recientes, o de clientes de base de datos como pgx, encontrarás generics por todas partes. Son el tipo de código que tiene sentido generalizar: operaciones fundamentales sobre colecciones, utilidades de concurrencia, deserializadores.
El patrón que ha emergido es claro. Las funciones genéricas se usan cuando se cumplen tres condiciones:
- La lógica es realmente idéntica para distintos tipos, no solo similar.
- Hay un rendimiento que perder con
anyy reflection, y el coste se nota. - La firma genérica es más clara para el usuario que una interfaz.
Fuera de esos casos, lo idiomático sigue siendo escribir la función concreta.
El caso de slices es ilustrativo. Esta librería estaba experimentalmente disponible desde hace tiempo y se promovió a la estándar en Go 1.21. Ofrece funciones como slices.Contains, slices.Index, slices.Sort, implementadas con generics. Cualquiera que hubiera escrito Go antes de 2022 había reimplementado estas funciones una y otra vez para cada tipo, porque no había alternativa razonable. Ahora son una línea, y ese es un beneficio real y cotidiano.
Lo que ha sorprendido
Un efecto que no se anticipó es el impacto que los generics han tenido en los clientes de base de datos. sqlc y, sobre todo, pgx en su versión 5 ofrecen escaneo tipo-seguro de filas a structs mediante generics. Antes, esto se hacía con reflection y una buena cantidad de ceremonia; ahora tienes una función pgx.CollectRows[T] que devuelve un []T directamente, y el compilador te avisa si la estructura no coincide con las columnas.
Este patrón se ha extendido a serialización JSON, a parseo de configuración y a varios frameworks de testing. Es el escenario donde los generics más añaden: conservas la verificación de tipos a través de una abstracción que antes la perdía por completo.
El otro sitio donde los generics han cuajado inesperadamente es en los canales tipados y las primitivas de concurrencia. Bibliotecas como sourcegraph/conc ofrecen pools de trabajadores, grupos de resultados y primitivas similares con generics, y la experiencia es cualitativamente mejor que el equivalente con interface{} y type assertions. Esta evolución del tipado fuerte en Go conecta con los cambios de PEP 695 que analizamos en Python 3.12.
Lo que no ha cuajado
La gran excepción es lo que algunos esperaban que fuera el caso estrella: los tipos de datos complejos genéricos, como árboles balanceados, heaps tipados, grafos. Esos existen en el ecosistema, pero su adopción ha sido moderada. La razón es simple: para colecciones de tamaño moderado, los slices y maps nativos son suficientes, y para casos que exigen estructuras especializadas, los desarrolladores prefieren librerías probadas con décadas de uso.
Los métodos genéricos son otra historia. Go no permite declarar métodos genéricos en un receptor no genérico (la constraint está en el tipo completo). Esto limita los patrones tipo “método Map en una colección” que otros lenguajes permiten. La comunidad ha aprendido a vivir con eso mediante funciones libres (Map(coll, fn) en lugar de coll.Map(fn)), pero es una rugosidad que probablemente se suavice en versiones futuras.
Mirando a dónde va
Tres años después, mi lectura es que Go ha absorbido los generics sin perder identidad. Han entrado en el sitio donde aportan valor real (librerías de bajo nivel, utilidades de colecciones, clientes de datos) y se han mantenido fuera del sitio donde habrían introducido ruido (código de negocio cotidiano, APIs públicas de servicios). Eso es bastante sano.
Lo que viene, según las propuestas que se están debatiendo, es flexibilizar algunos casos concretos: permitir más inferencia de tipos genéricos en casos donde hoy hay que anotarlos explícitamente, y quizá permitir métodos genéricos en algunas circunstancias limitadas. Nada de esto rompe el modelo actual, solo lo pule.
Conclusión
Si alguien me pregunta si debería empezar a usar generics en su código Go, mi respuesta sigue siendo la de hace tres años: primero escribe la función concreta. Si luego te encuentras copiándola para tres tipos distintos y la lógica es idéntica, entonces sí, generalízala. Ese criterio modesto es lo que mejor ha aguantado.
Los generics en Go son una herramienta de infraestructura de librerías, no una herramienta de código de aplicación. Entender ese sitio específico, ni más grande ni más pequeño, es lo que hace que su adopción sea exitosa.