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,stringe tipos personalizados, eliminando assim completamente a necessidade de asserções de tipointerface{}.
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á
- Sintaxe de funções genéricas
- Estruturas de dados genéricas
- Restrições
anyecomparable constraints: Restrições personalizadas- Inferência de tipos
- Comparação entre o genérico e o
interface{}
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.”
// 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
// 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
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)
}
(2) Sintaxe de funções genéricas
// 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
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
}
(2) ▶ Exemplo: Restrições personalizadas
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)
}
(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 } |
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
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
}
(2) ▶ Exemplo: Conjunto genérico
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())
}
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
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)
}
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
// 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)
}
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
anyeinterface{}? R:anyé um alias parainterface{}(type any = interface{}), e os dois são totalmente equivalentes. O Go 1.18 introduziu genéricos juntamente comanycomo um alias de tipo. Recomenda-se usaranyem restrições genéricas einterface{}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, masfunc (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]eStack[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
- Função genérica:
func Name[T Constraint](param T) T - Restrições de tipo:
any/comparable/Ordered/ personalizado - Restrição personalizada:
interface { ~int | ~string } - Inferência de tipo: inferida automaticamente pelo compilador ou especificada explicitamente
- Estruturas de dados genéricas:
Stack[T]/Set[T]/List[T] - Os métodos não podem ter parâmetros de tipo adicionais
- Sem sobrecarga de tempo de execução (expansão em tempo de compilação)
📝 Exercícios
-
Básico (Dificuldade ⭐): Escreva uma função genérica
Find[T comparable](slice []T, target T) intque retorne o índice detargetemslice; setargetnão existir, retorne -1. Verifique se ela funciona para os três tipos:int,stringefloat64. -
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. -
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 callbackOnEvicted(chamado quando uma chave é excluída ou expira); (5) Use genéricos para garantir que o tipo da chave seja comparável.