ProgramaciónDesarrollador Backend

¿Cómo se implementan las genéricas (generics) en Go? ¿Cuáles son las limitaciones, la sintaxis, los inconvenientes y los casos de uso adecuados?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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:

  • Seguridad de tipos — exclusión de errores de tipos durante la compilación.
  • Limitaciones (constraints) — posibilidad de restringir las genéricas solo a tipos que implementen ciertas interfaces o que sean comparables.
  • Compatibilidad — los desarrolladores principiantes pueden utilizar genéricas según sea necesario sin reestructurar proyectos de inmediato.

Preguntas engañosas.

¿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) }

Errores comunes y antipatróns

  • Uso de interface{} en lugar de generics en las nuevas versiones de Go.
  • Olvidar especificar restricciones (constraints), lo que lleva a la imposibilidad de utilizar operaciones dentro del código universal.
  • Generalización excesiva de funciones simples (genéricos por el simple hecho de generalizar).

Ejemplo de la vida real

Caso negativo

Dentro de una biblioteca para trabajar con colecciones, se implementó una funcionalidad de Map universal a través de interface{}:

Pros:

  • Universal, se puede utilizar para cualquier tipo.

Contras:

  • Ausencia de seguridad de tipos, necesidad de conversiones manuales, errores en tiempo de ejecución.

Caso positivo

En el mismo proyecto, se pasó a genéricas y se definieron restricciones a través de interfaces:

Pros:

  • Seguridad de tipos, los errores se detectan durante la compilación, simplificación del mantenimiento.

Contras:

  • Se requiere conocimiento de la nueva sintaxis, algunas IDEs no soportan bien las constraints complejas.