Las genéricas o generics aparecieron en Go a partir de la versión 1.18. Durante mucho tiempo, Go fue considerado un lenguaje conservador, donde se excluyeron las genéricas por simplicidad, pero con el aumento en el número de proyectos y el desarrollo del ecosistema surgió la necesidad de escribir funciones y estructuras universales. Esto es especialmente crítico para estructuras de contenedor, algoritmos de procesamiento de colecciones y código de infraestructura.
Problema: antes de la aparición de generics, era necesario duplicar código o utilizar interfaces vacías (interface{}), lo que resultaba en la pérdida de la seguridad de tipos y disminución del rendimiento, así como en la complicación del proceso de depuración.
Solución: las genéricas se implementaron en Go mediante parámetros de tipo que se especifican entre corchetes en funciones y tipos. A través de constraints es posible limitar los parámetros de tipo permitidos. Esto permite escribir funciones genéricas sin perder la seguridad de tipos.
Ejemplo de código:
package main import "fmt" type Adder[T any] func(a, b T) T func Sum[T any](slice []T, add Adder[T]) T { var result T for _, v := range slice { result = add(result, v) } return result } func intAdder(a, b int) int { return a + b } func main() { nums := []int{1, 2, 3, 4} sum := Sum(nums, intAdder) fmt.Println(sum) }
Características clave:
¿Se pueden usar operaciones aritméticas (+, -, *, /) para cualquier parámetro de tipo T en generics?
No. El compilador de Go no sabe si el tipo del parámetro admite operaciones aritméticas. Para ello, es necesario especificar un constraint, por ejemplo, una interfaz con restricciones de operador a partir de Go 1.18+.
Ejemplo de código:
type Addable interface { int | float64 | uint } func Sum[T Addable](slice []T) T { var result T for _, v := range slice { result += v } return result }
¿Pueden los contenedores genéricos contener métodos con comportamientos diferentes para diferentes tipos?
No. Los métodos de las estructuras o funciones genéricas son idénticos para todos los tipos, a menos que se utilice un type switch dentro del método. El comportamiento debe estar definido a través de constraints o ser completamente parametrizado.
¿Se pueden crear tipos con parámetros de tipo (tipos genéricos) a nivel de paquete y no solo de funciones?
Sí, a partir de Go 1.18, se pueden crear tanto funciones genéricas como tipos estructurales genéricos:
type Stack[T any] struct { items []T } func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
Dentro de una biblioteca para trabajar con colecciones, se implementó una funcionalidad de Map universal a través de interface{}:
Pros:
Contras:
En el mismo proyecto, se pasó a genéricas y se definieron restricciones a través de interfaces:
Pros:
Contras: