Go Generics en 2026: Parámetros de Tipo, Restricciones y Preguntas de Entrevista

Guía completa sobre generics en Go: parámetros de tipo, interfaces de restricción, patrones avanzados y preguntas técnicas frecuentes en entrevistas de trabajo para desarrolladores Go.

Go Generics en 2026: Parámetros de Tipo, Restricciones y Preguntas de Entrevista

Los generics representan una de las evoluciones más significativas del lenguaje Go desde su creación. Introducidos con Go 1.18 en 2022, los parámetros de tipo permiten escribir código reutilizable sin sacrificar la seguridad del tipado estático. En 2026, los generics se han convertido en una herramienta indispensable para todo desarrollador Go, y su dominio constituye un criterio de evaluación fundamental durante las entrevistas técnicas. Este artículo explora en profundidad los parámetros de tipo, las restricciones de interfaz, los patrones avanzados y las preguntas de entrevista más frecuentes.

Los generics resuelven el problema de la duplicación de código

Antes de Go 1.18, los desarrolladores debían duplicar el código para cada tipo o utilizar interface con aserciones de tipo costosas en rendimiento y propensas a errores en tiempo de ejecución. Los generics permiten escribir una única implementación que funciona con cualquier tipo que satisfaga las restricciones especificadas, manteniendo la verificación de tipos en tiempo de compilación.

Sintaxis de los Parámetros de Tipo

La sintaxis de generics en Go utiliza corchetes para declarar los parámetros de tipo. Un parámetro de tipo se define con un nombre seguido de una restricción que especifica las operaciones permitidas sobre ese tipo.

go
package main

import "fmt"

// Generic function with type parameter T
func Print[T any](value T) {
    fmt.Println(value)
}

// Generic function with multiple type parameters
func Map[T, U any](slice []T, fn func(T) U) []U {
    result := make([]U, len(slice))
    for i, v := range slice {
        result[i] = fn(v)
    }
    return result
}

func main() {
    Print(42)
    Print("hello")
    Print(3.14)

    numbers := []int{1, 2, 3, 4, 5}
    doubled := Map(numbers, func(n int) int { return n * 2 })
    fmt.Println(doubled) // [2 4 6 8 10]

    strings := Map(numbers, func(n int) string {
        return fmt.Sprintf("num:%d", n)
    })
    fmt.Println(strings) // [num:1 num:2 num:3 num:4 num:5]
}

La palabra clave any es un alias para interface{} y representa la restricción más permisiva. Cuando un parámetro de tipo utiliza any, no se garantiza ninguna operación específica sobre ese tipo, excepto la asignación y el paso como parámetro.

Restricciones de Interfaz y Tipos Predefinidos

Las restricciones permiten limitar los tipos aceptados por una función o estructura genérica. Go 1.18 introdujo el paquete constraints (movido a cmp y slices en Go 1.21+) así como la posibilidad de definir restricciones personalizadas.

go
package main

import (
    "cmp"
    "fmt"
)

// Custom constraint using type union
type Number interface {
    int | int32 | int64 | float32 | float64
}

// Function constrained to Number types
func Sum[T Number](numbers []T) T {
    var total T
    for _, n := range numbers {
        total += n
    }
    return total
}

// Using cmp.Ordered for comparable types
func Max[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// Constraint with method requirement
type Stringer interface {
    String() string
}

func PrintAll[T Stringer](items []T) {
    for _, item := range items {
        fmt.Println(item.String())
    }
}

func main() {
    ints := []int{1, 2, 3, 4, 5}
    fmt.Println(Sum(ints)) // 15

    floats := []float64{1.5, 2.5, 3.0}
    fmt.Println(Sum(floats)) // 7.0

    fmt.Println(Max(10, 20))       // 20
    fmt.Println(Max("abc", "xyz")) // xyz
}

El operador | permite crear uniones de tipos en una restricción. El operador ~ (tilde) permite incluir todos los tipos cuyo tipo subyacente coincida, lo cual es esencial para tipos personalizados basados en tipos primitivos.

Estructuras y Métodos Genéricos

Las estructuras también pueden utilizar parámetros de tipo, permitiendo crear contenedores y estructuras de datos reutilizables.

go
package main

import "fmt"

// Generic Stack implementation
type Stack[T any] struct {
    items []T
}

func NewStack[T any]() *Stack[T] {
    return &Stack[T]{items: make([]T, 0)}
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    item := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return item, true
}

func (s *Stack[T]) Peek() (T, bool) {
    if len(s.items) == 0 {
        var zero T
        return zero, false
    }
    return s.items[len(s.items)-1], true
}

func (s *Stack[T]) Len() int {
    return len(s.items)
}

// Generic Pair type
type Pair[K, V any] struct {
    Key   K
    Value V
}

func main() {
    // Stack of integers
    intStack := NewStack[int]()
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)

    for intStack.Len() > 0 {
        val, _ := intStack.Pop()
        fmt.Println(val) // 3, 2, 1
    }

    // Stack of strings
    strStack := NewStack[string]()
    strStack.Push("hello")
    strStack.Push("world")

    // Pair usage
    pair := Pair[string, int]{Key: "age", Value: 30}
    fmt.Printf("%s: %d\n", pair.Key, pair.Value)
}

Los métodos genéricos no pueden introducir nuevos parámetros de tipo; utilizan los parámetros declarados a nivel de la estructura.

Inferencia de Tipo e Instanciación

El compilador de Go puede inferir los parámetros de tipo a partir de los argumentos proporcionados, simplificando la llamada de funciones genéricas.

go
package main

import "fmt"

func First[T any](slice []T) (T, bool) {
    if len(slice) == 0 {
        var zero T
        return zero, false
    }
    return slice[0], true
}

func Keys[K comparable, V any](m map[K]V) []K {
    keys := make([]K, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    return keys
}

func main() {
    // Type inference - no need to specify [int]
    nums := []int{1, 2, 3}
    first, ok := First(nums)
    fmt.Println(first, ok) // 1 true

    // Explicit type parameters when needed
    empty, ok := First[string]([]string{})
    fmt.Println(empty, ok) // "" false

    // Map with inferred types
    ages := map[string]int{"alice": 30, "bob": 25}
    names := Keys(ages)
    fmt.Println(names) // [alice bob] or [bob alice]
}

La inferencia de tipo funciona de izquierda a derecha y no puede inferir tipos que no aparecen en los parámetros de la función.

Patrones Avanzados: Interfaces Genéricas y Composición

Las interfaces pueden ser parametrizadas, permitiendo definir contratos genéricos reutilizables.

go
package main

import "fmt"

// Generic Repository interface
type Repository[T any, ID comparable] interface {
    FindByID(id ID) (T, error)
    Save(entity T) error
    Delete(id ID) error
    FindAll() ([]T, error)
}

// Generic Result type for error handling
type Result[T any] struct {
    value T
    err   error
}

func Ok[T any](value T) Result[T] {
    return Result[T]{value: value}
}

func Err[T any](err error) Result[T] {
    return Result[T]{err: err}
}

func (r Result[T]) Unwrap() (T, error) {
    return r.value, r.err
}

func (r Result[T]) Map(fn func(T) T) Result[T] {
    if r.err != nil {
        return r
    }
    return Ok(fn(r.value))
}

// Generic Option type
type Option[T any] struct {
    value *T
}

func Some[T any](value T) Option[T] {
    return Option[T]{value: &value}
}

func None[T any]() Option[T] {
    return Option[T]{value: nil}
}

func (o Option[T]) IsSome() bool {
    return o.value != nil
}

func (o Option[T]) Unwrap() T {
    if o.value == nil {
        panic("called Unwrap on None")
    }
    return *o.value
}

func (o Option[T]) UnwrapOr(defaultValue T) T {
    if o.value == nil {
        return defaultValue
    }
    return *o.value
}

func main() {
    result := Ok(42).Map(func(n int) int { return n * 2 })
    val, err := result.Unwrap()
    fmt.Println(val, err) // 84 <nil>

    opt := Some("hello")
    fmt.Println(opt.IsSome())      // true
    fmt.Println(opt.Unwrap())      // hello

    none := None[int]()
    fmt.Println(none.UnwrapOr(0))  // 0
}

Preguntas de Entrevista Frecuentes sobre Generics en Go

Las entrevistas técnicas en Go incluyen cada vez más preguntas sobre generics. A continuación se presentan las preguntas más comunes con sus respuestas esperadas.

Pregunta 1: ¿Cuál es la diferencia entre any y comparable?

any es un alias para interface{} y acepta cualquier tipo. comparable es una restricción predefinida que solo acepta tipos que soportan los operadores == y !=. Los slices, maps y funciones no son comparables y no satisfacen comparable.

Pregunta 2: ¿Por qué no se pueden usar operadores aritméticos con any?

Los operadores aritméticos (+, -, *, /) no están definidos para todos los tipos. Se debe usar una restricción como Number o cmp.Ordered para garantizar que el tipo soporte estas operaciones.

go
// This won't compile - any doesn't support +
func AddWrong[T any](a, b T) T {
    return a + b // compile error
}

// Correct approach with constraint
type Numeric interface {
    ~int | ~int64 | ~float64
}

func Add[T Numeric](a, b T) T {
    return a + b // works
}

Pregunta 3: ¿Cómo implementar una función genérica Filter?

go
func Filter[T any](slice []T, predicate func(T) bool) []T {
    result := make([]T, 0)
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Usage
numbers := []int{1, 2, 3, 4, 5, 6}
evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
// evens = [2, 4, 6]

Pregunta 4: ¿Cuál es la diferencia entre ~int e int en una restricción?

El operador tilde (~) incluye todos los tipos cuyo tipo subyacente es int, incluyendo tipos definidos como type MyInt int. Sin tilde, solo se acepta el tipo exacto int.

go
type MyInt int

// Only accepts int, not MyInt
type StrictInt interface {
    int
}

// Accepts int and any type with underlying type int (like MyInt)
type FlexibleInt interface {
    ~int
}

¿Listo para aprobar tus entrevistas de Go?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Limitaciones y Buenas Prácticas

Los generics en Go tienen ciertas limitaciones intencionales para preservar la simplicidad del lenguaje. Los métodos no pueden tener sus propios parámetros de tipo. La especialización de tipo (proporcionar una implementación diferente para un tipo específico) no está soportada. Los tipos recursivos con parámetros de tipo tienen restricciones.

Las buenas prácticas recomiendan usar generics cuando realmente simplifican el código, no simplemente porque están disponibles. Si una función solo funciona con dos o tres tipos concretos, funciones separadas pueden ser más claras. Los generics son particularmente útiles para estructuras de datos, algoritmos de colección y patrones como Result/Option.

Conclusión

Los generics transforman la forma en que el código Go se escribe y organiza. Permiten crear bibliotecas y abstracciones reutilizables mientras se conserva la seguridad de tipos que hace fuerte a Go. El dominio de los parámetros de tipo, las restricciones y los patrones asociados se ha vuelto esencial para todo desarrollador Go profesional. Las preguntas de entrevista sobre este tema evalúan no solo el conocimiento de la sintaxis, sino también la capacidad de reconocer cuándo los generics aportan valor real al código.

Compartir

Artículos relacionados