A logo showing the text blog.marcnuri.com
English
Inicio»Go»Generics en Go: Introducción Completa a Type Parameters en Go 1.18+

Entradas Recientes

  • Fabric8 Kubernetes Client 7.4.0 está disponible!
  • Kubernetes MCP Server se une a la organización Containers
  • MCP Tool Annotations: Añadiendo Metadatos y Contexto a Tus Herramientas de IA
  • Fabric8 Kubernetes Client 7.2.0 está disponible!
  • Conectarse a un servidor MCP con JavaScript y AI SDK

Categorías

  • Antiguo
  • Front-end
  • Go
  • Herramientas
  • Industria y negocios
  • Inteligencia Artificial
  • Java
  • JavaScript
  • Operaciones
  • Personal
  • Proyectos personales

Archivos

  • septiembre 2025
  • julio 2025
  • mayo 2025
  • abril 2025
  • marzo 2025
  • febrero 2025
  • enero 2025
  • diciembre 2024
  • noviembre 2024
  • agosto 2024
  • junio 2024
  • mayo 2024
  • abril 2024
  • marzo 2024
  • febrero 2024
  • enero 2024
  • diciembre 2023
  • noviembre 2023
  • octubre 2023
  • septiembre 2023
  • agosto 2023
  • julio 2023
  • junio 2023
  • mayo 2023
  • abril 2023
  • marzo 2023
  • febrero 2023
  • enero 2023
  • diciembre 2022
  • noviembre 2022
  • octubre 2022
  • septiembre 2022
  • agosto 2022
  • julio 2022
  • junio 2022
  • mayo 2022
  • marzo 2022
  • febrero 2022
  • enero 2022
  • diciembre 2021
  • noviembre 2021
  • octubre 2021
  • septiembre 2021
  • agosto 2021
  • julio 2021
  • enero 2021
  • diciembre 2020
  • octubre 2020
  • septiembre 2020
  • agosto 2020
  • junio 2020
  • mayo 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • noviembre 2019
  • octubre 2019
  • julio 2019
  • diciembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • marzo 2018
  • febrero 2018
  • noviembre 2017
  • octubre 2017
  • agosto 2017
  • julio 2017
  • enero 2017
  • julio 2016
  • enero 2016
  • diciembre 2015
  • noviembre 2015
  • diciembre 2014
  • marzo 2014
  • febrero 2011
  • junio 2008
  • mayo 2008
  • abril 2008
  • enero 2008
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007

Generics en Go: Introducción Completa a Type Parameters en Go 1.18+

2022-03-26 en Go etiquetado Go por Marc Nuri | Última actualización: 2025-09-15
English version

Introducción

Los generics de Go fueron una de las características más esperadas en Go 1.18, cambiando fundamentalmente la forma en que escribimos código reutilizable y type-safe. Antes de los generics, los desarrolladores de Go a menudo dependían de interface, reflection o generación de código para lograr flexibilidad de tipos, lo que implicaba compromisos en rendimiento, type safety o problemas de mantenimiento.

Con la introducción de parameter types, Go ahora proporciona una forma limpia y eficiente de escribir funciones genéricas y estructuras de datos mientras mantiene la seguridad de tipado en tiempo de compilación. En este artículo, te mostraré todo lo que necesitas saber sobre los generics de Go, desde conceptos básicos hasta patrones avanzados, con ejemplos prácticos que puedes usar en código de producción.

Ya estés construyendo funciones de utilidad, implementando estructuras de datos personalizadas o diseñando APIs que funcionen con múltiples tipos, entender los generics hará que tu código Go sea más expresivo y mantenible.

Entendiendo los Fundamentos de los Generics de Go

Los generics de Go introducen el concepto de parameter types, que son marcadores de posición para tipos que se especifican cuando se usa la función o tipo genérico. Esto te permite escribir código que funciona con diferentes tipos mientras mantiene la seguridad de tipado en tiempo de compilación.

Tu Primera Función Genérica

Comencemos con un ejemplo sencillo que demuestra el concepto central usando algunas comparaciones básicas:

basic_generics.go
package main

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

// Función genérica que funciona con cualquier tipo ordenado
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    // Comparar las puntuaciones de Alex y Aitana (int)
    alexScore := 95
    aitanaScore := 87
    topScore := Max(alexScore, aitanaScore)
    fmt.Printf("Puntuación más alta: %d\n", topScore)

    // Comparar dos nombres alfabéticamente (string)
    lastName := Max("Alex", "Aitana")
    fmt.Printf("Nombre que viene último: %s\n", lastName)

    // Comparar temperaturas (float64)
    morningTemp := 15.5
    afternoonTemp := 22.3
    higherTemp := Max(morningTemp, afternoonTemp)
    fmt.Printf("Temperatura más alta: %.1f°C\n", higherTemp)
}

En este ejemplo, Max[T constraints.Ordered] define una función genérica donde:

  • T es el type parameter (puede ser cualquier nombre, T es lo convencional)
  • constraints.Ordered es una restricción de tipo que limita T a tipos que soportan operadores de comparación (<, <=, >, >=) así como igualdad (==, !=)
  • La función funciona con cualquier tipo que satisfaga la restricción constraints.Ordered

Nota

La restricción constraints.Ordered incluye tipos como integers, floats y strings que pueden compararse para ordenamiento. Para operaciones solo de igualdad, usa la restricción incorporada comparable en su lugar.

Al usar generics, podemos escribir una sola función Max que funciona con múltiples tipos sin duplicar código o perder type safety.

Inferencia de Tipos

Una de las fortalezas de los generics de Go es la inferencia de tipos (type inference), el compilador a menudo puede deducir los parameter types a partir de los argumentos de la función:

type_inference.go
package main

import "fmt"

func Swap[T any](a, b T) (T, T) {
    return b, a
}

func main() {
    // Especificación explícita de tipo
    x1, y1 := Swap[int](1, 2)
    fmt.Printf("Explícito: %d, %d\n", x1, y1)

    // Inferencia de tipos (recomendado)
    x2, y2 := Swap("hola", "mundo")
    fmt.Printf("Inferido: %s, %s\n", x2, y2)
}

La restricción any (alias para interface{}) permite cualquier tipo. En la mayoría de los casos, puedes omitir el parámetro de tipo al llamar funciones genéricas, ya que el compilador de Go inferirá el tipo apropiado.

En este ejemplo, Swap funciona con cualquier tipo, y el compilador infiere el tipo basándose en los argumentos proporcionados. La única restricción es que ambos parámetros deben ser del mismo tipo.

Restricciones de Tipo e Interfaces

Las restricciones de tipo definen qué operaciones están permitidas en los parámetros de tipo. Go 1.18 introdujo el paquete golang.org/x/exp/constraints con las restricciones más comunes. También puedes definir restricciones personalizadas usando interfaces.

Restricciones Incorporadas y Estándar

Go proporciona varias restricciones incorporadas y de la biblioteca estándar para ayudarte a escribir código genérico de forma segura y eficiente. Estas restricciones, como comparable y las del paquete golang.org/x/exp/constraints, te permiten restringir parámetros de tipo a tipos que soportan operaciones específicas como comparación o aritmética.

Veamos algunos ejemplos:

standard_constraints.go
package main

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

// Usando restricciones incorporadas
func Add[T constraints.Ordered](a, b T) T {
    return a + b
}

// Usando restricciones numéricas
func Multiply[T constraints.Integer | constraints.Float](a, b T) T {
    return a * b
}

func main() {
    // Funciona con cualquier tipo ordenado (enteros, floats, strings)
    result1 := Add(10, 20)
    result2 := Add(1.5, 2.5)
    result3 := Add("Hola, ", "Go!")

    fmt.Printf("Resultados de Add: %d, %.1f, %s\n", result1, result2, result3)

    // Funciona solo con tipos numéricos
    product1 := Multiply(5, 4)
    product2 := Multiply(2.5, 3.0)

    fmt.Printf("Resultados de Multiply: %d, %.1f\n", product1, product2)
}

En este ejemplo:

  • La función Add usa la restricción constraints.Ordered, permitiéndole funcionar con cualquier tipo que soporte ordenamiento (integers, floats, strings).
  • La función Multiply usa una unión de constraints.Integer y constraints.Float, restringiéndola solo a tipos numéricos.

Las restricciones incorporadas cubren casos de uso comunes, pero también puedes crear tus propias restricciones personalizadas para requerimientos más específicos. Veamos cómo hacer eso.

Restricciones de Tipo Personalizadas

Puedes definir restricciones personalizadas usando tipos de interfaz:

custom_constraints.go
package main

import "fmt"

// Restricción personalizada para tipos que pueden convertirse a string
type Stringer interface {
    String() string
}

// Restricción personalizada usando unión de tipos
type Numeric interface {
    int | int32 | int64 | float32 | float64
}

// Función genérica usando restricción personalizada
func PrintValue[T Stringer](value T) {
    fmt.Println("Valor:", value.String())
}

// Función genérica con restricción numérica
func Average[T Numeric](values []T) T {
    if len(values) == 0 {
        var zero T
        return zero
    }

    var sum T
    for _, v := range values {
        sum += v
    }
    return sum / T(len(values))
}

// Tipo de ejemplo implementando Stringer
type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%s (%d años)", p.Name, p.Age)
}

func main() {
    person := Person{Name: "Julia", Age: 30}
    PrintValue(person)

    intValues := []int{1, 2, 3, 4, 5}
    floatValues := []float64{1.1, 2.2, 3.3}

    fmt.Printf("Promedio int: %d\n", Average(intValues))
    fmt.Printf("Promedio float: %.1f\n", Average(floatValues))
}

En este ejemplo podemos ver varios conceptos importantes:

  • Restricciones basadas en interfaces: Stringer requiere que los tipos implementen un método String().
  • Restricciones de unión de tipos: Numeric acepta múltiples tipos específicos usando el operador |.
  • Implementación de tipo personalizado: el tipo Person implementa la interfaz Stringer.

Patrones Genéricos Avanzados

A medida que te sientas más cómodo con los generics básicos, puedes probar patrones más sofisticados que aprovechan todo el poder del sistema de tipos de Go. Estos patrones se vuelven particularmente útiles cuando construyes aplicaciones complejas que necesitan trabajar con múltiples tipos mientras mantienen seguridad de tipado muy estricta.

Múltiples Parámetros de Tipo

Las funciones y tipos pueden tener múltiples parámetros de tipo:

multiple_type_params.go
package main

import "fmt"

// Función map genérica con dos parámetros de tipo
func MapSlice[T any, U any](slice []T, mapper func(T) U) []U {
    result := make([]U, len(slice))
    for i, item := range slice {
        result[i] = mapper(item)
    }
    return result
}

// Par clave-valor genérico
type Pair[K comparable, V any] struct {
    Key   K
    Value V
}

// Cache genérico con múltiples parámetros de tipo
type Cache[K comparable, V any] struct {
    data map[K]V
}

func NewCache[K comparable, V any]() *Cache[K, V] {
    return &Cache[K, V]{
        data: make(map[K]V),
    }
}

func (c *Cache[K, V]) Set(key K, value V) {
    c.data[key] = value
}

func (c *Cache[K, V]) Get(key K) (V, bool) {
    value, exists := c.data[key]
    return value, exists
}

func (c *Cache[K, V]) GetAll() []Pair[K, V] {
    pairs := make([]Pair[K, V], 0, len(c.data))
    for k, v := range c.data {
        pairs = append(pairs, Pair[K, V]{Key: k, Value: v})
    }
    return pairs
}

func main() {
    // Mapear enteros a strings
    numbers := []int{1, 2, 3, 4, 5}
    strings := MapSlice(numbers, func(n int) string {
        return fmt.Sprintf("Número: %d", n)
    })
    fmt.Printf("Strings mapeados: %v\n", strings)

    // Cache string-to-int
    intCache := NewCache[string, int]()
    intCache.Set("edad", 42)
    intCache.Set("año", 2023)

    if value, exists := intCache.Get("edad"); exists {
        fmt.Printf("Edad: %d\n", value)
    }

    // Cache int-to-string
    stringCache := NewCache[int, string]()
    stringCache.Set(1, "uno")
    stringCache.Set(2, "dos")

    fmt.Printf("Contenido del cache de strings: %v\n", stringCache.GetAll())
}

En este ejemplo, podemos ver cómo definir funciones y tipos con múltiples parameter types:

  • MapSlice[T any, U any] mapea un slice de tipo T a un slice de tipo U usando una función de mapeo proporcionada.
  • Pair[K comparable, V any] representa un par clave-valor con tipos genéricos de clave y valor.
  • Cache[K comparable, V any] es un caché simple en memoria que puede almacenar valores de cualquier tipo indexados por claves de cualquier tipo comparable.
    Implementa un método GetAll que devuelve todos los pares clave-valor como un slice de Pair[K, V], este es un buen ejemplo para ver cómo usar generics en métodos de struct.

Este patrón es muy útil para construir estructuras de datos reutilizables y algoritmos que pueden operar en varios tipos sin sacrificar type safety.

Restricciones de Tipo con Métodos

Puedes crear restricciones que requieran métodos específicos definiendo interfaces:

method_constraints.go
package main

import (
    "fmt"
    "math"
)

// Distanceable es una interfaz usada como Restricción que requiere un método Distance
type Distanceable interface {
    Distance() float64
}

// Struct Point2D que implementa Distanceable
type Point2D struct {
    X, Y float64
}

func (p Point2D) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

// Struct Point3D que implementa Distanceable
type Point3D struct {
    X, Y, Z float64
}

func (p Point3D) Distance() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y + p.Z*p.Z)
}

// Función genérica para filtrar puntos dentro de una distancia
func FilterWithinDistance[T Distanceable](points []T, maxDistance float64) []T {
    result := make([]T, 0)
    for _, point := range points {
        if point.Distance() <= maxDistance {
            result = append(result, point)
        }
    }
    return result
}

func main() {
    // Puntos 2D
    points2D := []Point2D{
        {X: 1, Y: 1},
        {X: 3, Y: 4},
        {X: 0.5, Y: 0.5},
        {X: 2, Y: 1},
    }

    near2D := FilterWithinDistance(points2D, 2.0)
    fmt.Printf("Puntos 2D dentro de distancia 2.0: %v\n", near2D)

    // Puntos 3D
    points3D := []Point3D{
        {X: 1, Y: 1, Z: 1},
        {X: 2, Y: 2, Z: 2},
        {X: 0.3, Y: 0.4, Z: 0.5},
    }

    near3D := FilterWithinDistance(points3D, 1.0)
    fmt.Printf("Puntos 3D dentro de distancia 3.0: %v\n", near3D)
}

En este ejemplo:

  • Definimos una interfaz Distanceable que requiere un método Distance().
  • Tanto los structs Point2D como Point3D implementan la interfaz Distanceable.
  • La función FilterWithinDistance[T Distanceable] filtra un slice de cualquier tipo que implemente Distanceable, devolviendo solo aquellos puntos dentro de una distancia máxima especificada.

Este patrón es potente para crear algoritmos que operan en cualquier tipo que satisfaga un comportamiento específico, como se define por los métodos en la interfaz.

Casos de Uso Reales

Un caso de uso práctico e ilustrativo para los generics de Go es construir clientes HTTP type-safe. Este patrón demuestra efectivamente los beneficios de los generics al eliminar aserciones de tipo repetitivas mientras mantiene la seguridad en tiempo de compilación a través de diferentes endpoints de API.

Manejador Genérico de Respuestas HTTP

El siguiente ejemplo demuestra un cliente HTTP genérico que puede manejar diferentes tipos de respuesta sin sacrificar type safety. Este enfoque es superior a usar interface{} porque el compilador valida los tipos en tiempo de compilación, previniendo panics en runtime y haciendo el código autodocumentado:

http_generics.go
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "strings"
)

// Estructura de respuesta genérica que envuelve cualquier tipo de datos
type APIResponse[T any] struct {
    Data    T      `json:"data"`
    Success bool   `json:"success"`
    Message string `json:"message"`
}

// Cliente HTTP que puede trabajar con cualquier tipo de respuesta de API
type HTTPClient[T any] struct {
    baseURL string
    client  *http.Client
}

func NewHTTPClient[T any](baseURL string) *HTTPClient[T] {
    return &HTTPClient[T]{
        baseURL: baseURL,
        client:  &http.Client{},
    }
}

// Método GET genérico que devuelve respuestas type-safe
func (h *HTTPClient[T]) Get(endpoint string) (*APIResponse[T], error) {
    url := h.baseURL + endpoint

    resp, err := h.client.Get(url)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var response APIResponse[T]
    if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
        return nil, err
    }

    return &response, nil
}

// Método POST genérico con tipos separados para request y response
func (h *HTTPClient[T]) Post(endpoint string, payload any) (*APIResponse[T], error) {
    url := h.baseURL + endpoint

    jsonData, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    resp, err := h.client.Post(url, "application/json", strings.NewReader(string(jsonData)))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()

    var response APIResponse[T]
    if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
        return nil, err
    }

    return &response, nil
}

// Estructuras de datos de ejemplo para la API de blog.marcnuri.com
type BlogPost struct {
    ID       int    `json:"id"`
    Title    string `json:"title"`
    Author   string `json:"author"`
    ReadTime string `json:"readTime"`
    URL string `json:"url"`
}

type CreatePostRequest struct {
    Title    string `json:"title"`
    Content  string `json:"content"`
    Author   string `json:"author"`
}

func main() {
    // Crear cliente para la API de blog.marcnuri.com
    client := NewHTTPClient[BlogPost]("https://api.blog.marcnuri.com")

    // Request GET type-safe - el compilador sabe que la respuesta contiene BlogPost
    if blogResponse, err := client.Get("/posts/1"); err == nil {
        fmt.Printf("Post del blog: %s por %s\n", blogResponse.Data.Title, blogResponse.Data.Author)
    }

    // Request POST type-safe con diferentes tipos de input/output
    newPost := CreatePostRequest{
        Title:   "Generics de Go en la Práctica",
        Content: "Una guía completa...",
        Author:  "Marc",
    }

    if createResponse, err := client.Post("/posts", newPost); err == nil {
        fmt.Printf("Post creado con ID: %d en %s\n", createResponse.Data.ID, createResponse.Data.URL)
    }
}

El ejemplo anterior muestra como trabajar con una API hipotética de blog.marcnuri.com, ilustrando cómo el mismo código de cliente HTTP puede manejar diferentes endpoints con completa type safety. Los tipos BlogPost y CreatePostRequest representan la estructura de datos intercambiados con la API, y el cliente genérico asegura que cada endpoint devuelva exactamente el tipo esperado.

Este patrón de cliente HTTP genérico ofrece varias ventajas:

  1. Type Safety: El compilador asegura tipos correctos en tiempo de compilación, eliminando errores de aserción de tipo en runtime.
  2. Reutilización de Código: Una implementación de cliente funciona con cualquier estructura de respuesta de API.
  3. Autodocumentado: Las firmas de métodos muestran claramente qué tipos se esperan y devuelven.
  4. Rendimiento: Sin overhead en runtime por aserciones de tipo o reflection.

En la práctica, este patrón es particularmente útil cuando construyes aplicaciones que interactúan con REST APIs, microservicios o cualquier sistema donde necesitas trabajar con datos estructurados de fuentes externas mientras mantienes las garantías de strong typing de Go.

Consideraciones de Rendimiento

Los generics de Go se implementan usando especialización de tipos en tiempo de compilación, lo que significa que típicamente no hay penalización de rendimiento en runtime comparado con implementaciones específicas de tipo:

performance_example.go
package main

import (
    "fmt"
    "time"
)

// Función sum genérica
func SumGeneric[T ~int](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

// Equivalente no genérico
func SumInt(values []int) int {
    var sum int
    for _, v := range values {
        sum += v
    }
    return sum
}

func benchmark(fn func()) time.Duration {
    start := time.Now()
    fn()
    return time.Since(start)
}

func main() {
    // Slice grande para benchmarking
    values := make([]int, 10000000)
    for i := range values {
        values[i] = i
    }

    genericSum := benchmark(func() {
        _ = SumGeneric(values)
    })
    fmt.Printf("Sum Genérico tomó: %v\n", genericSum)

    nonGenericSum := benchmark(func() {
        _ = SumInt(values)
    })
    fmt.Printf("Sum No Genérico tomó: %v\n", nonGenericSum)

    // El rendimiento debería ser típicamente idéntico
    fmt.Println("El rendimiento de las versiones genérica y no genérica debería ser casi idéntico")
}

El benchmark de rendimiento demuestra que los generics en Go tienen cero overhead en runtime. Esto es porque Go implementa generics a través de especialización de tipos en tiempo de compilación en lugar de type erasure en runtime (como Java) o virtual dispatch.

Cuando el compilador de Go encuentra SumGeneric[int], genera código especializado equivalente a la función no genérica SumInt. Esto significa que obtienes los beneficios de type safety y reutilización de los generics sin contraprestaciones o penalización en el rendimiento. El código ensamblador generado para ambas funciones será virtualmente idéntico.

Nota

El benchmark simple anterior es principalmente para ilustración. Para mediciones de rendimiento precisas, usa el paquete testing de Go con go test -bench para análisis estadístico adecuado y resultados más confiables.

Mejores Prácticas

Cuando uses generics de Go, seguir las mejores prácticas te ayudará a escribir código limpio, mantenible y eficiente.

Aquí hay algunas pautas a considerar:

  1. Usa nombres de restricción significativos: Elige nombres descriptivos para tus parameter types y restricciones.
  2. Prefiere restricciones específicas: Usa la restricción más específica posible en lugar de any.
  3. Mantén los parameter types al mínimo: No agregues parameter types a menos que proporcionen un valor claro.
  4. Usa inferencia de tipos: Permite que sea el compilador el que infiera tipos siempre que sea posible.
  5. Considera la legibilidad: Los generics deberían hacer el código más reutilizable sin sacrificar claridad.

El siguiente fragmento de código ilustra algunas de estas mejores prácticas con ejemplos:

best_practices.go
package main

import "golang.org/x/exp/constraints"

// ✅ Bueno: Nombre de restricción descriptivo y restricción específica
func FindMax[Number constraints.Ordered](values []Number) (Number, bool) {
    if len(values) == 0 {
        var zero Number
        return zero, false
    }

    max := values[0]
    for _, v := range values[1:] {
        if v > max {
            max = v
        }
    }
    return max, true
}

// ❌ Menos ideal: Restricción genérica cuando una específica funcionaría
func FindMaxAny[T any](values []T, compare func(a, b T) bool) (T, bool) {
    if len(values) == 0 {
        var zero T
        return zero, false
    }

    max := values[0]
    for _, v := range values[1:] {
        if compare(v, max) {
            max = v
        }
    }
    return max, true
}

// ✅ Bueno: Uso de inferencia de tipos
func ExampleUsage() {
    numbers := []int{1, 5, 3, 9, 2}
    // El tipo se infiere, no necesitas especificar [int]
    max, found := FindMax(numbers)
    if found {
        println("Max:", max)
    }
}

Ahora que hemos cubierto las mejores prácticas, veamos algunos errores comunes y cómo evitarlos.

Errores Comunes y Cómo Evitarlos

Aunque los generics de Go son muy potentes, hay varios errores comunes que los desarrolladores cometen cuando los adoptan por primera vez. Entender estas trampas te ayudará a escribir código genérico más limpio y mantenible y evitar errores de compilación frustrantes.

Código Excesivamente Genérico

No hagas todo genérico. Usa generics cuando tengas una necesidad clara de flexibilidad de tipos:

avoid_over_generics.go
package main

// ❌ Malo: Generics innecesarios
func PrintGeneric[T any](value T) {
    println(value) // ¡Esto no compila! println no acepta any
}

// ✅ Mejor: Usa generics solo cuando sea necesario
func PrintString(value string) {
    println(value)
}

// ✅ Bueno: Los generics proporcionan valor claro
func SafeGet[T any](slice []T, index int) (T, bool) {
    var zero T
    if index < 0 || index >= len(slice) {
        return zero, false
    }
    return slice[index], true
}

Confusión con Restricciones de Tipo

Entiende qué operaciones permiten tus restricciones:

constraint_confusion.go
package main

import "golang.org/x/exp/constraints"

// ❌ Esto no compilará - 'any' no soporta comparación
// func Compare[T any](a, b T) bool {
//     return a == b
// }

// ✅ Restricción correcta para comparación
func Compare[T comparable](a, b T) bool {
    return a == b
}

// ✅ Restricción correcta para ordenamiento
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

Manejo de Valores Cero

Ten cuidado al devolver valores cero de tipos genéricos:

zero_value_handling.go
package main

import "errors"

// ✅ Bueno: Manejo claro de errores con valores cero
func GetFirst[T any](slice []T) (T, error) {
    var zero T
    if len(slice) == 0 {
        return zero, errors.New("slice está vacío")
    }
    return slice[0], nil
}

// ✅ Alternativa: Usa punteros para valores opcionales
func GetFirstPtr[T any](slice []T) *T {
    if len(slice) == 0 {
        return nil
    }
    return &slice[0]
}

func main() {
    numbers := []int{1, 2, 3}

    // Usando retorno de error
    if first, err := GetFirst(numbers); err == nil {
        println("Primero:", first)
    }

    // Usando retorno de puntero
    if first := GetFirstPtr(numbers); first != nil {
        println("Primero:", *first)
    }
}

Conclusión

Los generics de Go proporcionan una forma potente de escribir código type-safe y reutilizable sin sacrificar rendimiento o la simplicidad de Go. La clave para usar generics efectivamente es entender cuándo agregan valor y aplicarlos juiciosamente.

Con la introducción de parameter types en Go 1.18, ahora puedes:

  • Escribir funciones y estructuras de datos que funcionen con múltiples tipos.
  • Mantener type safety en tiempo de compilación.
  • Reducir duplicación de código.
  • Construir APIs más expresivas.

Comienza identificando patrones repetidos en tus proyectos donde los generics podrían eliminar duplicación. Enfócate en funciones de utilidad, estructuras de datos y APIs que naturalmente funcionen con múltiples tipos. Recuerda que el mejor código genérico es código que se siente natural y mejora la mantenibilidad sin agregar complejidad innecesaria.

A medida que te sientes más cómodo con los generics, prueba patrones avanzados como restricciones personalizadas y múltiples parámetros de tipo.

También te podría interesar

  • Patrones de Concurrencia en Go: Goroutines y Channels
  • Cómo configurar y limpiar pruebas unitarias en Go
  • Introducción a Testcontainers para Go
  • Pruebas unitarias para APIs REST basadas en Go Gin Web Framework con httptest
Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Navegador de artículos
Eclipse JKube 1.8.0 está disponible!Pattern Matching para instanceof en Java
© 2007 - 2025 Marc Nuri