Introdução: o que são Generics e por que surgiram em Go?
Por muito tempo, a comunidade Go (Golang) viveu sem genéricos (programação genérica). Os desenvolvedores usavam interfaces, interface{} vazios e geração de código para contornar as limitações da tipagem estrita. Na versão Go 1.18 (fevereiro de 2022), a linguagem finalmente recebeu genéricos — uma ferramenta poderosa que permite escrever funções e estruturas que funcionam com diferentes tipos de dados sem perder a segurança de tipos.
Neste artigo, vamos entender para que servem os Generics em Go, como funcionam e aprender a aplicá-los na prática. O material é voltado para desenvolvedores iniciantes que já conhecem os fundamentos de Go: variáveis, funções e slices.
Para que servem os genéricos em Go?
Imagine que você precisa escrever uma função que encontra o valor mínimo em um slice de números. Sem genéricos, você teria que escrever funções separadas para int, float64 e outros tipos:
// Sem genéricos — três funções idênticasfunc MinInt(a, b int) int { if a < b { return a } return b}
func MinFloat64(a, b float64) float64 { if a < b { return a } return b}Isso leva à duplicação de código. Antes, esse problema era resolvido com interface{} e conversão de tipos, mas essa abordagem perdia a segurança em tempo de compilação:
func Min(a, b interface{}) interface{} { // Comparação e conversão — fácil errar return a}Os genéricos resolvem três problemas principais:
- Segurança de tipos — erros de tipo são detectados em tempo de compilação.
- Eliminação de duplicação — uma função funciona com qualquer tipo.
- Legibilidade do código — indicação explícita de quais tipos são suportados.
Fundamentos da sintaxe: type parameters e constraints
Os genéricos em Go são baseados em dois conceitos: parâmetros de tipo (type parameters) e restrições (constraints).
Parâmetros de tipo
Os parâmetros de tipo são especificados entre colchetes após o nome da função ou tipo. Por convenção, geralmente se usa a letra T (de Type), mas qualquer nome pode ser usado:
// Função com um parâmetro de tipo Tfunc Print[T any](value T) { fmt.Println(value)}
// UsoPrint[int](42) // especificamos o tipo explicitamentePrint("Hello") // o compilador infere o tipo automaticamenteA palavra-chave any é um alias para a interface vazia interface{}. Ela significa que a função aceita qualquer tipo.
Restrições (constraints)
As restrições definem quais tipos podem ser usados com um genérico. Por exemplo, para comparar números, o tipo precisa suportar os operadores < e >. As restrições embutidas para tipos numéricos estão no pacote golang.org/x/exp/constraints:
import "golang.org/x/exp/constraints"
// Restrição — apenas números inteirosfunc Sum[T constraints.Integer](numbers []T) T { var total T for _, n := range numbers { total += n } return total}Você também pode criar suas próprias restrições usando interfaces:
// Restrição personalizada: o tipo deve ter um método String()type Stringer interface { String() string}
func Describe[T Stringer](value T) string { return "Value: " + value.String()}Exemplos práticos de uso de Generics
Vamos ver alguns cenários reais onde os genéricos simplificam significativamente o código.
1. Função universal de busca em slice
// Encontra o índice de um elemento em um slice. -1 se não encontrado.func Index[T comparable](slice []T, target T