Go: Go Generics

Última atualização: 2026-08-26

A versão 1.18 do Go introduziu os genéricos — permitindo que funções e estruturas de dados sejam escritas usando parâmetros de tipo compatíveis com int, string e tipos personalizados, eliminando assim completamente a necessidade de asserções de tipo interface{}.

Quando você precisa escrever três funções de classificação quase idênticas para int, string e float64, os genéricos permitem que você escreva apenas uma.

1. Você aprenderá


2. A história real de um engenheiro de algoritmos

(1) Desafio: Escrever uma função de ordenação para cada tipo

Charlie precisa implementar uma biblioteca genérica de classificação que suporte três tipos: int, float64 e string:

“Não havia genéricos no Go antes da versão 1.18. Eu escrevi três funções idênticas — a única diferença era o tipo. Toda vez que eu adicionava um novo tipo, precisava copiar e colar o código. O esforço de manutenção era altíssimo.”

GO
// Bad code: no generics, copy-paste for each type
func SortInts(slice []int) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortFloat64s(slice []float64) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

func SortStrings(slice []string) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}
// Every new type is Ctrl+C / Ctrl+V

(2) Solução em Go 1.18: Genéricos

GO
// Good code: one function supports all ordered types
func Sort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
}

// Usage: automatic type inference
ints := []int{3, 1, 2}
Sort(ints)

floats := []float64{3.14, 1.41, 2.72}
Sort(floats)

strs := []string{"c", "a", "b"}
Sort(strs)
// No need for three functions!

(3) Desempenho: Antes dos genéricos x Depois dos genéricos

Dimensão interface{} + asserção de tipo genéricos
Quantidade de código Uma cópia por tipo Uma cópia
Segurança de tipos ❌ Erro grave em tempo de execução ✅ Verificação em tempo de compilação
Desempenho Envolve sobrecarga de empacotamento/desempacotamento ✅ Sem sobrecarga
Legibilidade Inúmeras asserções de tipo ✅ Claro

3. Funções genéricas

(1) ▶ Exemplo: Funções genéricas básicas

⚙️ Pré-requisito: Executar go get golang.org/x/exp/constraints

GO
package main

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

// Generic function: T is the type parameter, any is the constraint (all types)
func Print[T any](value T) {
    fmt.Println(value)
}

// Multiple type parameters
func Pair[A, B any](a A, b B) (A, B) {
    return a, b
}

// Constrained to ordered types
func Max[T constraints.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

func main() {
    // Explicitly specify type parameters
    Print[int](42)
    Print[string]("hello")

    // Type inference (compiler automatically infers T)
    Print(42)       // T = int
    Print("hello")  // T = string

    fmt.Println(Max(3, 5))           // T = int → 5
    fmt.Println(Max(3.14, 2.72))    // T = float64 → 3.14
    fmt.Println(Max("apple", "banana")) // T = string → "banana"

    a, b := Pair(1, "one")
    fmt.Printf("A=%v (type: %T), B=%v (type: %T)\n", a, a, b, b)
}
▶ Experimente

(2) Sintaxe de funções genéricas

GO
// Syntax: func Name[TypeParameter Constraint](paramList) returnType
func Name[T Constraint](param T) T { ... }

// Type parameter list is enclosed in [] (not angle brackets)
// Constraints can be any / comparable / custom interface
// Return values can use type parameters

4. Restrições de tipo

(1) ▶ Exemplo: Restrições embutidas

GO
package main

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

// any: all types (equivalent to interface{})
func Identity[T any](value T) T {
    return value
}

// comparable: types that support == and !=
func Contains[T comparable](slice []T, target T) bool {
    for _, v := range slice {
        if v == target {
            return true
        }
    }
    return false
}

// constraints.Ordered: types that support < <= > >=
func Min[T constraints.Ordered](a, b T) T {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(Contains([]int{1, 2, 3}, 2))         // true
    fmt.Println(Contains([]string{"a", "b", "c"}, "d")) // false
    fmt.Println(Min(10, 20))                            // 10
}
▶ Experimente

(2) ▶ Exemplo: Restrições personalizadas

GO
package main

import "fmt"

// Custom constraint: interface + type set
type Numeric interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64 |
        ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
        ~float32 | ~float64
}

// ~int means all types whose underlying type is int (including type MyInt int)
// int only matches int itself

type Price float64

func Sum[T Numeric](values []T) T {
    var sum T
    for _, v := range values {
        sum += v
    }
    return sum
}

func main() {
    ints := []int{1, 2, 3, 4, 5}
    fmt.Printf("Sum(ints) = %d\n", Sum(ints)) // 15

    floats := []float64{1.5, 2.5, 3.0}
    fmt.Printf("Sum(floats) = %.1f\n", Sum(floats)) // 7.0

    prices := []Price{10.99, 20.99, 5.00}
    fmt.Printf("Sum(prices) = %.2f\n", Sum(prices)) // 36.98 (underlying type float64)
}
▶ Experimente

(3) Níveis de restrição

restrição Operações suportadas Origem
any Todas as operações (sem restrições) Integrado
comparable == != Integrado
constraints.Ordered < <= > >= golang.org/x/exp
constraints.Integer Todos os tipos inteiros golang.org/x/exp
constraints.Float Todos os tipos de ponto flutuante golang.org/x/exp
Personalizado Tipo de união interface { ~int | ~string }
💡 Dica: any é equivalente a interface{}, e comparable é uma restrição embutida (não é necessário importar). constraints.Ordered, constraints.Integer e outras estão localizadas no pacote golang.org/x/exp/constraints — isso é experimental, mas se tornou um padrão de fato. A partir do Go 1.21+, algumas restrições foram transferidas para a biblioteca padrão.


5. Estruturas de dados genéricas

(1) ▶ Exemplo: Stack genérico

GO 📖 Somente leitura
package main

import "fmt"

// Stack — generic stack
type Stack[T any] struct {
    items []T
}

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]) IsEmpty() bool {
    return len(s.items) == 0
}

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

func main() {
    // int stack
    intStack := Stack[int]{}
    intStack.Push(1)
    intStack.Push(2)
    intStack.Push(3)

    for !intStack.IsEmpty() {
        if val, ok := intStack.Pop(); ok {
            fmt.Printf("Popped: %d\n", val)
        }
    }

    // string stack
    strStack := Stack[string]{}
    strStack.Push("hello")
    strStack.Push("world")
    fmt.Printf("Peek: %s\n", strStack.Peek()) // world
}
45 linhas de lógica (limite de 40, somente leitura)

(2) ▶ Exemplo: Conjunto genérico

GO 📖 Somente leitura
package main

import "fmt"

// Set — generic collection (comparable constraint)
type Set[T comparable] struct {
    items map[T]struct{}
}

func NewSet[T comparable]() *Set[T] {
    return &Set[T]{items: make(map[T]struct{})}
}

func (s *Set[T]) Add(item T) {
    s.items[item] = struct{}{}
}

func (s *Set[T]) Remove(item T) {
    delete(s.items, item)
}

func (s *Set[T]) Contains(item T) bool {
    _, ok := s.items[item]
    return ok
}

func (s *Set[T]) Size() int {
    return len(s.items)
}

func (s *Set[T]) Items() []T {
    result := make([]T, 0, len(s.items))
    for item := range s.items {
        result = append(result, item)
    }
    return result
}

// Union — set union (method receivers cannot have additional type parameters)
func Union[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        result.Add(item)
    }
    for _, item := range b.Items() {
        result.Add(item)
    }
    return result
}

// Intersection — set intersection
func Intersection[T comparable](a, b *Set[T]) *Set[T] {
    result := NewSet[T]()
    for _, item := range a.Items() {
        if b.Contains(item) {
            result.Add(item)
        }
    }
    return result
}

func main() {
    set1 := NewSet[int]()
    set1.Add(1)
    set1.Add(2)
    set1.Add(3)

    set2 := NewSet[int]()
    set2.Add(3)
    set2.Add(4)
    set2.Add(5)

    fmt.Println("Set1:", set1.Items())
    fmt.Println("Set2:", set2.Items())
    fmt.Println("Union:", Union(set1, set2).Items())
    fmt.Println("Intersection:", Intersection(set1, set2).Items())
}
61 linhas de lógica (limite de 40, somente leitura)
🔥 Erro comum: Métodos genéricos de estruturas não podem declarar parâmetros de tipo adicionais — apenas os parâmetros de tipo da própria estrutura estão disponíveis. Se forem necessários parâmetros de tipo adicionais, use uma função (por exemplo, Union[T comparable](a, b *Set[T])). Além disso, tipos genéricos não podem ser usados diretamente em declarações const.


6. Inferência de tipos e instanciação

(1) ▶ Exemplo: Inferência de tipos

GO
package main

import (
    "fmt"
    "strconv"
)

func Map[T, U any](input []T, fn func(T) U) []U {
    result := make([]U, len(input))
    for i, v := range input {
        result[i] = fn(v)
    }
    return result
}

type Stack[T any] struct {
    items []T
}

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

func main() {
    // Type inference: T=int, U=string
    nums := []int{1, 2, 3, 4, 5}
    strs := Map(nums, strconv.Itoa)
    fmt.Println(strs) // ["1", "2", "3", "4", "5"]

    // Explicitly specify type parameters (when inference fails)
    explicit := Map[int, string](nums, strconv.Itoa)
    fmt.Println(explicit)

    // ❌ Type parameters can only be used for function params/return values, not variables
    // var list List[int]  ← type instantiation

    // Type instantiation: create a concrete type from a generic
    var intStack Stack[int]
    intStack.Push(10)
}
▶ Experimente
100%
graph TB
    A[Generic function definition<br/>func Max[T Ordered](a, b T) T] --> B{Call Max(3, 5)}
    B --> C[Compiler infers T = int]
    C --> D[Instantiate Max[int]]
    D --> E[int version: func Max(a, b int) int]
    B --> F{Call Max(3.14, 2.72)}
    F --> G[Compiler infers T = float64]
    G --> H[Instantiate Max[float64]]
    H --> I[float64 version: func Max(a, b float64) float64]

(2) Genéricos x interface{}

Comparação interface{} + asserção de tipo genéricos
Segurança de tipos ❌ Erro grave em tempo de execução ✅ Verificação em tempo de compilação
Desempenho Boxing/unboxing (fuga para o heap) ✅ Sem sobrecarga (expansão em tempo de compilação)
Quantidade de código Uma cópia por tipo ✅ Uma cópia do código genérico
Flexibilidade É possível armazenar tipos diferentes na mesma fatia ✅ O tipo é determinado em tempo de compilação
Complexidade Fácil de entender ⚠️ Sintaxe complexa

7. Exemplo completo: Biblioteca genérica de classificação

(1) ▶ Exemplo: Implementação completa

GO 📖 Somente leitura
// generic_sort.go
package main

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

// ---------- Sorting functions ----------

// SortSlice sorts a slice of any ordered type
func SortSlice[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] < slice[j]
    })
}

// ReverseSort sorts in descending order
func ReverseSort[T constraints.Ordered](slice []T) {
    sort.Slice(slice, func(i, j int) bool {
        return slice[i] > slice[j]
    })
}

// ---------- Search functions ----------

// BinarySearch performs binary search (requires sorted input)
func BinarySearch[T constraints.Ordered](slice []T, target T) (int, bool) {
    low, high := 0, len(slice)-1
    for low <= high {
        mid := low + (high-low)/2
        if slice[mid] == target {
            return mid, true
        } else if slice[mid] < target {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }
    return -1, false
}

// ---------- Aggregation functions ----------

// Filter filters elements
func Filter[T any](slice []T, predicate func(T) bool) []T {
    var result []T
    for _, v := range slice {
        if predicate(v) {
            result = append(result, v)
        }
    }
    return result
}

// Reduce aggregates elements
func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
    result := initial
    for _, v := range slice {
        result = fn(result, v)
    }
    return result
}

type Person struct {
    Name string
    Age  int
}

func main() {
    // 1. Sort integers
    ints := []int{5, 2, 8, 1, 9, 3}
    SortSlice(ints)
    fmt.Printf("Sorted ints: %v\n", ints)

    // 2. Sort in descending order
    ReverseSort(ints)
    fmt.Printf("Reverse: %v\n", ints)

    // 3. Sort strings
    strs := []string{"banana", "apple", "cherry", "date"}
    SortSlice(strs)
    fmt.Printf("Sorted strings: %v\n", strs)

    // 4. Binary search
    idx, found := BinarySearch(ints, 5)
    fmt.Printf("BinarySearch 5: idx=%d, found=%v\n", idx, found)

    // 5. Filter
    evens := Filter(ints, func(n int) bool { return n%2 == 0 })
    fmt.Printf("Evens: %v\n", evens)

    // 6. Reduce
    sum := Reduce(ints, 0, func(acc, n int) int { return acc + n })
    fmt.Printf("Sum: %d\n", sum)

    // 7. Custom type (Person doesn't implement Ordered, can't sort directly)
    // Need a custom sort function
    people := []Person{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }

    // Use a closure for custom sorting
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Printf("Sorted by age: %v\n", people)
}
75 linhas de lógica (limite de 40, somente leitura)
💡 Dica: sort.Slice em si não é uma função genérica — ela aceita fatias any ([]any) e funciona por meio de reflexão. No entanto, quando usada com closures, ela se integra bem com genéricos. A classificação genérica verdadeira vem do slices.Sort (golang.org/x/exp/slices), um novo recurso introduzido no Go 1.21, que é implementado inteiramente por meio de genéricos.


❓ Perguntas Frequentes

P: Quando os genéricos foram introduzidos? R: No Go 1.18 (lançado em março de 2022). A equipe do Go passou dez anos projetando uma solução para genéricos e, por fim, optou pela abordagem dos “parâmetros de tipo”, em vez das abordagens baseadas em modelos do C++ ou na “apagamento de tipos” do Java. Eles são expandidos em tempo de compilação, sem nenhuma sobrecarga em tempo de execução.

P: Qual é a sintaxe para parâmetros de tipo? R: func Name[T Constraint](param T) T. Os parâmetros de tipo são declarados usando colchetes [] (não colchetes angulares). É permitido usar vários parâmetros: func Map[T, U any](input []T, fn func(T) U) []U.

P: Como se escrevem restrições? R: interface { set of types }. Restrições embutidas: any (qualquer tipo), comparable (comparável). De terceiros: constraints.Ordered (ordenado). Personalizadas: interface { ~int | ~string }.

P: Qual é a diferença entre any e interface{}? R: any é um alias para interface{} (type any = interface{}), e os dois são totalmente equivalentes. O Go 1.18 introduziu genéricos juntamente com any como um alias de tipo. Recomenda-se usar any em restrições genéricas e interface{} em código comum.

P: Como funciona a inferência de tipos? R: O compilador infere automaticamente os parâmetros de tipo com base nos argumentos da função. Por exemplo, Max(3, 5) → infere T=int. Se a inferência falhar ou se você quiser especificar o tipo explicitamente, pode escrever: Max[int](3, 5). Os parâmetros de tipo não podem ser inferidos a partir do tipo de retorno — pelo menos um parâmetro deve envolver um parâmetro de tipo.

P: É possível usar genéricos em métodos? R: Os métodos não podem ter parâmetros de tipo adicionais — apenas parâmetros de tipo struct podem ser usados. Portanto, func (s *Stack[T]) Push(item T) é válido, mas func (s *Stack[T]) Convert[U any]() U é inválido. Se forem necessários parâmetros de tipo adicionais, use uma função comum em vez de um método.

P: Como fica o desempenho dos genéricos em comparação? R: Sem sobrecarga de tempo de execução. Os genéricos do Go geram implementações específicas para cada combinação de parâmetros de tipo em tempo de compilação (monomorfização). Portanto, Stack[int] e Stack[string] são tipos completamente diferentes em tempo de execução, sem boxing nem unboxing. O único custo é um ligeiro aumento no tempo de compilação e um tamanho maior do binário.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Escreva uma função genérica Find[T comparable](slice []T, target T) int que retorne o índice de target em slice; se target não existir, retorne -1. Verifique se ela funciona para os três tipos: int, string e float64.

  2. Avançado (Dificuldade ⭐⭐): Implemente uma Queue[T any] genérica (fila do tipo “primeiro a entrar, primeiro a sair”). Requisitos: (1) Métodos Enqueue, Dequeue, Peek e IsEmpty; (2) Suporte a qualquer tipo; (3) Implementar um buffer circular para evitar redimensionamentos frequentes; (4) Verificar a segurança concorrente usando -race.

  3. Desafio (Dificuldade ⭐⭐⭐): Implemente um cache seguro contra concorrência genérico Cache[K comparable, V any]. Requisitos: (1) Métodos Get/Set/Delete/Clear; (2) Proteção por RWMutex; (3) Mecanismo de expiração TTL; (4) Suporte para o callback OnEvicted (chamado quando uma chave é excluída ou expira); (5) Use genéricos para garantir que o tipo da chave seja comparável.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%