Menu

Genéricos en Golang: parámetros de tipo, restricciones y ejemplos

Los genéricos (Go 1.18+) permiten que una función o un tipo funcione con muchos tipos sin perder la seguridad de tipos. Aprende los parámetros de tipo, las restricciones any, comparable y cmp.Ordered, restricciones propias con ~, tipos genéricos y cuándo no usarlos.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una función genérica

Los parámetros de tipo van entre corchetes antes de los parámetros normales. Cada uno tiene una restricción que dice qué tipos se permiten.

T y U se infieren de los argumentos, así que rara vez escribes Map[int, string](...). La inferencia funciona a partir de los argumentos de la función; no puede inferir un tipo que solo aparece en el resultado. Para func Zero[T any]() T, tienes que escribir Zero[int]().

Antes de Go 1.18 esta función había que escribirla una vez por tipo, o recibir []interface{} y perder la seguridad de tipos.

Restricciones

Una restricción es una interfaz. Limita qué tipos se pueden usar y, a cambio, le dice al compilador qué operaciones se permiten sobre los valores de ese tipo.

RestricciónAdmiteTe permite usar
anycualquier tipoasignar, pasar, guardar
comparabletipos que admiten ====, !=, claves de map
cmp.Ordered (Go 1.21)enteros, flotantes, strings<, <=, >, >=, además de ==
una interfaz con métodostipos con esos métodosllamar a los métodos
una unión de tipos como ~int | ~float64exactamente esos tiposlos operadores que admiten todos ellos

Con any en lugar de comparable, v == target no compila: invalid operation: v == target (incomparable types in type set). La restricción es lo que hace legal el operador.

Estas dos funciones auxiliares ya existen: slices.Index y slices.Max. Los paquetes estándar slices, maps y cmp cubren la mayoría de las necesidades genéricas del día a día, así que revísalos antes de escribir las tuyas.

Restricciones propias con uniones de tipos y ~

Una restricción puede enumerar tipos con |. Los operadores permitidos son los que admiten todos los tipos enumerados.

La tilde importa. ~int64 significa "cualquier tipo cuyo tipo subyacente sea int64", así que se acepta el tipo con nombre Cents. Escrito como int64 a secas, Sum([]Cents{...}) falla con Cents does not satisfy Number (possibly missing ~ for int64 in Number).

Las interfaces que contienen uniones de tipos solo se pueden usar como restricciones, nunca como tipos normales de variable. var n Number no compila.

El paquete golang.org/x/exp/constraints tiene restricciones ya hechas como Integer, Float y Signed. Está fuera de la librería estándar, así que el entorno de ejecución de aquí no puede importarlo; cmp.Ordered es la estándar.

Tipos genéricos

Los structs, slices, maps y otros tipos también pueden tener parámetros de tipo. Los métodos de un tipo genérico usan los parámetros del receptor.

Dos detalles de este ejemplo aparecen constantemente:

  • El valor cero de T. var zero T es la forma de devolver "nada" desde código genérico. No hay un T{} ni un nil que funcione para todos los tipos.
  • Instanciación. Un tipo genérico tiene que instanciarse antes de usarlo: Stack[string], no Stack. Dentro de sus propios métodos, el receptor se escribe Stack[T].

Go 1.24 añadió los alias de tipo genéricos: type Set[T comparable] = map[T]struct{} ahora compila.

Límites con los que te vas a encontrar

  • Los métodos no tienen parámetros de tipo propios. func (s *Stack[T]) Map[U any](...) no está permitido. Usa una función de nivel superior.
  • Sin especialización. No puedes escribir una implementación aparte para T = string. Un type switch sobre any(v) dentro de la función es el apaño, y normalmente una señal de que los genéricos no son la herramienta adecuada.
  • Sin restricciones de operadores por nombre. No puedes decir "cualquier tipo con un método +"; enumeras los tipos en una unión.
  • El acceso a campos a través de una restricción no funciona. Aunque todos los tipos de una unión tengan un campo ID, v.ID no está permitido. Pon un método en la restricción.

Cuándo no usar genéricos

Los genéricos encajan en tipos contenedor (pilas, conjuntos, cachés, árboles) y en algoritmos sobre slices y maps que hacen lo mismo para cualquier tipo de elemento. No encajan en todas partes.

  • Un solo tipo concreto. Si la función solo se llama con []User, una versión genérica añade coste de lectura y nada más.
  • Comportamiento que cambia según el tipo. Para eso están las interfaces. Una función que recibe un io.Writer es más sencilla que una que recibe [T io.Writer], y funciona igual.
  • Sustituir parámetros de interfaz. func Print[T fmt.Stringer](v T) no es mejor que func Print(v fmt.Stringer).

La pauta del propio equipo de Go: escribe primero el código para un tipo concreto, y recurre a los parámetros de tipo cuando te veas escribiendo el mismo código por segunda vez cambiando solo los tipos.

Errores comunes

  • Usar any donde hace falta comparable o cmp.Ordered. El compilador rechaza == o < sobre un parámetro de tipo any.
  • Olvidar ~. Los tipos definidos por el usuario como type ID int no cumplen una restricción int a secas.
  • Devolver nil para un parámetro de tipo. No está permitido salvo que la restricción limite T a tipos de tipo puntero. Devuelve un valor cero.
  • Escribir una función auxiliar genérica que ya existe. Revisa antes slices, maps y cmp.

Preguntas frecuentes

¿Tiene Go genéricos?

Sí, desde Go 1.18 (marzo de 2022). Las funciones y los tipos pueden declarar parámetros de tipo entre corchetes: func Map[T, U any](s []T, f func(T) U) []U. Cada parámetro de tipo tiene una restricción, que es una interfaz que describe lo que el tipo debe admitir.

¿Qué diferencia hay entre any y comparable en los genéricos de Go?

any admite cualquier tipo, pero apenas te deja hacer nada con los valores salvo asignarlos, pasarlos y guardarlos. comparable admite solo los tipos que soportan == y !=, que es lo que necesitas para claves de map o comprobaciones de igualdad. Para < y >, usa cmp.Ordered.

¿Qué significa la tilde (~) en una restricción de Go?

~T significa "cualquier tipo cuyo tipo subyacente sea T". ~int coincide con int y también con type Celsius int. Sin la tilde, int en una restricción coincide solo con el propio int, así que los tipos definidos por el usuario se rechazarían.

¿Pueden los métodos de Go tener parámetros de tipo?

No. Un método puede usar los parámetros de tipo de su tipo receptor (func (s *Stack[T]) Push(v T)), pero no puede declarar otros nuevos. Escribe en su lugar una función genérica de nivel superior: func Map[T, U any](s *Stack[T], f func(T) U) *Stack[U].

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR