Go Generics em 2026: Parâmetros de Tipo, Constraints e Perguntas de Entrevista

Guia completo sobre generics em Go: parâmetros de tipo, interfaces de constraint, padrões avançados e perguntas técnicas frequentes em entrevistas de emprego para desenvolvedores Go.

Go Generics em 2026: Parâmetros de Tipo, Constraints e Perguntas de Entrevista

Os generics representam uma das evoluções mais significativas da linguagem Go desde sua criação. Introduzidos com Go 1.18 em 2022, os parâmetros de tipo permitem escrever código reutilizável sem sacrificar a segurança da tipagem estática. Em 2026, os generics se tornaram uma ferramenta indispensável para todo desenvolvedor Go, e seu domínio constitui um critério de avaliação fundamental durante as entrevistas técnicas. Este artigo explora em profundidade os parâmetros de tipo, as constraints de interface, os padrões avançados e as perguntas de entrevista mais frequentes.

Os generics resolvem o problema da duplicação de código

Antes do Go 1.18, os desenvolvedores precisavam duplicar o código para cada tipo ou utilizar interface com asserções de tipo custosas em performance e propensas a erros em tempo de execução. Os generics permitem escrever uma única implementação que funciona com qualquer tipo que satisfaça as constraints especificadas, mantendo a verificação de tipos em tempo de compilação.

Sintaxe dos Parâmetros de Tipo

A sintaxe de generics em Go utiliza colchetes para declarar os parâmetros de tipo. Um parâmetro de tipo é definido com um nome seguido de uma constraint que especifica as operações permitidas sobre esse 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]
}

A palavra-chave any é um alias para interface{} e representa a constraint mais permissiva. Quando um parâmetro de tipo utiliza any, nenhuma operação específica é garantida sobre esse tipo, exceto a atribuição e a passagem como parâmetro.

Constraints de Interface e Tipos Predefinidos

As constraints permitem restringir os tipos aceitos por uma função ou struct genérica. Go 1.18 introduziu o pacote constraints (movido para cmp e slices no Go 1.21+) assim como a possibilidade de definir constraints 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
}

O operador | permite criar uniões de tipos em uma constraint. O operador ~ (til) permite incluir todos os tipos cujo tipo subjacente corresponda, o que é essencial para tipos personalizados baseados em tipos primitivos.

Structs e Métodos Genéricos

As structs também podem utilizar parâmetros de tipo, permitindo criar containers e estruturas de dados reutilizáveis.

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

Os métodos genéricos não podem introduzir novos parâmetros de tipo; eles utilizam os parâmetros declarados no nível da struct.

Inferência de Tipo e Instanciação

O compilador Go pode inferir os parâmetros de tipo a partir dos argumentos fornecidos, simplificando a chamada de funções 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]
}

A inferência de tipo funciona da esquerda para a direita e não pode inferir tipos que não aparecem nos parâmetros da função.

Padrões Avançados: Interfaces Genéricas e Composição

As interfaces podem ser parametrizadas, permitindo definir contratos genéricos reutilizáveis.

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
}

Perguntas de Entrevista Frequentes sobre Generics em Go

As entrevistas técnicas em Go incluem cada vez mais perguntas sobre generics. A seguir estão as perguntas mais comuns com suas respostas esperadas.

Pergunta 1: Qual é a diferença entre any e comparable?

any é um alias para interface{} e aceita qualquer tipo. comparable é uma constraint predefinida que aceita apenas tipos que suportam os operadores == e !=. Slices, maps e funções não são comparáveis e não satisfazem comparable.

Pergunta 2: Por que não se pode usar operadores aritméticos com any?

Os operadores aritméticos (+, -, *, /) não estão definidos para todos os tipos. É necessário usar uma constraint como Number ou cmp.Ordered para garantir que o tipo suporte essas operações.

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
}

Pergunta 3: Como implementar uma função 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]

Pergunta 4: Qual é a diferença entre ~int e int em uma constraint?

O operador til (~) inclui todos os tipos cujo tipo subjacente é int, incluindo tipos definidos como type MyInt int. Sem til, apenas o tipo exato int é aceito.

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
}

Pronto para mandar bem nas entrevistas de Go?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Limitações e Boas Práticas

Os generics em Go têm certas limitações intencionais para preservar a simplicidade da linguagem. Os métodos não podem ter seus próprios parâmetros de tipo. A especialização de tipo (fornecer uma implementação diferente para um tipo específico) não é suportada. Tipos recursivos com parâmetros de tipo têm restrições.

As boas práticas recomendam usar generics quando eles realmente simplificam o código, não simplesmente porque estão disponíveis. Se uma função funciona apenas com dois ou três tipos concretos, funções separadas podem ser mais claras. Os generics são particularmente úteis para estruturas de dados, algoritmos de coleção e padrões como Result/Option.

Conclusão

Os generics transformam a forma como o código Go é escrito e organizado. Eles permitem criar bibliotecas e abstrações reutilizáveis enquanto conservam a segurança de tipos que torna o Go forte. O domínio dos parâmetros de tipo, das constraints e dos padrões associados se tornou essencial para todo desenvolvedor Go profissional. As perguntas de entrevista sobre este tema avaliam não apenas o conhecimento da sintaxe, mas também a capacidade de reconhecer quando os generics agregam valor real ao código.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Desenvolvedor fullstack, fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 8 de julho de 2026

Compartilhar

Artigos relacionados