Generics en Go: para qué sirven y cómo usarlos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: qué son los Generics y por qué aparecieron en Go



Durante mucho tiempo, la comunidad de Go prescindió de los genéricos (tipos parametrizados). Los desarrolladores usaban interface{} y conversiones de tipo manuales, lo que generaba código verboso y errores en tiempo de ejecución. Con el lanzamiento de la versión Go 1.18 (febrero de 2022), se añadieron oficialmente al lenguaje los genéricos, una de las funciones más esperadas.



Los Generics (o polimorfismo paramétrico) permiten escribir funciones y estructuras de datos que funcionan con cualquier tipo, manteniendo al mismo tiempo un tipado estricto en tiempo de compilación. Esto significa que puedes escribir un algoritmo (por ejemplo, ordenación o búsqueda) y usarlo para int, string, estructuras personalizadas, sin duplicar código y sin perder la seguridad de tipos.



En este artículo, analizaremos para qué sirven los genéricos en Go, cómo usarlos correctamente y qué aspectos tener en cuenta.



Parte principal: cómo funcionan los Generics en Go



1. Sintaxis de los parámetros de tipo



La sintaxis básica de los genéricos en Go es la siguiente: después del nombre de la función o tipo, entre corchetes, se indican los parámetros de tipo. Veamos un ejemplo muy simple: una función que devuelve el valor recibido (análogo a identity):



package main

import "fmt"

// Función genérica: T es un parámetro de tipofunc Print[T any](value T) { fmt.Println(value)}

func main() { Print[int](42) // indicación explícita del tipo Print[string]("Go") // indicación explícita del tipo Print("Go 1.18") // inferencia de tipo implícita (type inference)}


La palabra clave any es un alias para interface{}. Significa que la función acepta un valor de cualquier tipo. Sin embargo, el verdadero poder de los genéricos se revela al usar restricciones (constraints).



2. Restricciones (constraints) e interfaces



Las restricciones permiten especificar qué operaciones se pueden realizar con el parámetro de tipo. Por ejemplo, para sumar dos números, es necesario garantizar que el tipo soporte los operadores +, -, *, etc. En Go, para esto se utilizan interfaces especiales del paquete golang.org/x/exp/constraints o interfaces personalizadas:



package main

import ( "fmt" "golang.org/x/exp/constraints")

// Restricción: T debe soportar comparaciónfunc Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b}

func main() { fmt.Println(Max(10, 20)) // 20 fmt.Println(Max("apple", "banana")) // banana // fmt.Println(Max([]int{1}, []int{2})) // Error de compilación: los slices no están ordenados}


La interfaz incorporada constraints.Ordered incluye tipos que soportan los operadores <, <=, >, >=: todos los tipos numéricos y string.



También puedes crear tus propias restricciones:



// Restricción personalizada: el tipo debe tener un método String()type Stringer interface {    String() string}

func Describe[T Stringer](v T) string { return "Value: " + v.String()}


3. Estructuras y métodos genéricos



Los genéricos en Go funcionan no solo con funciones, sino también con tipos personalizados. Veamos un ejemplo clásico: una pila genérica:



package main

import "fmt"

// Estructura genérica Stacktype Stack[T any] struct { items []T}

// Push añade un elemento a la pilafunc (s *Stack[T]) Push(item T) { s.items = append(s.items, item)}

// Pop extrae un elemento de la pilafunc (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}

func main()

Blogs

Book Recommendations