Introdução: o que são Generics e por que eles surgiram no Go
Por muito tempo, a comunidade Go lidou sem genéricos (tipos genéricos). Os desenvolvedores usavam interface{} e conversão manual de tipos, o que resultava em código verboso e erros em tempo de execução. Com o lançamento da versão Go 1.18 (fevereiro de 2022), os genéricos foram oficialmente adicionados à linguagem — um dos recursos mais esperados.
Generics (ou polimorfismo paramétrico) permitem escrever funções e estruturas de dados que funcionam com qualquer tipo, mantendo a tipagem estrita em tempo de compilação. Isso significa que você pode escrever um único algoritmo (por exemplo, ordenação ou busca) e usá-lo para int, string, structs personalizadas — sem duplicação de código e sem perder a segurança de tipos.
Neste artigo, vamos explorar por que os genéricos são necessários no Go, como usá-los corretamente e quais armadilhas devem ser consideradas.
Parte principal: como os Generics funcionam no Go
1. Sintaxe dos parâmetros de tipo
A sintaxe básica dos genéricos no Go é a seguinte: após o nome da função ou tipo, entre colchetes, são especificados os parâmetros de tipo. Vejamos um exemplo simples — uma função que retorna o valor recebido (análogo a identity):
package main
import "fmt"
// Função genérica: T é um parâmetro de tipofunc Print[T any](value T) { fmt.Println(value)}
func main() { Print[int](42) // especificação explícita de tipo Print[string]("Go") // especificação explícita de tipo Print("Go 1.18") // inferência implícita de tipo (type inference)}A palavra-chave any é um alias para interface{}. Ela significa que a função aceita um valor de qualquer tipo. No entanto, o verdadeiro poder dos genéricos é revelado com o uso de restrições (constraints).
2. Restrições (constraints) e interfaces
As restrições permitem especificar quais operações podem ser realizadas com o parâmetro de tipo. Por exemplo, para somar dois números, é necessário garantir que o tipo suporte os operadores +, -, *, etc. No Go, isso é feito usando interfaces especiais do pacote golang.org/x/exp/constraints ou interfaces personalizadas:
package main
import ( "fmt" "golang.org/x/exp/constraints")
// Restrição: T deve suportar comparaçãofunc Max[T constraints.Ordered](a, b T) T { if a > b { return a } return b}
func main() { fmt.Println(Max(10, 20)) // 20 fmt.Println(Max("apple", "banana")) // banana // fmt.Println(Max([]int{1}, []int{2})) // Erro de compilação: slices não são ordenados}A interface embutida constraints.Ordered inclui tipos que suportam os operadores <, <=, >, >=: todos os tipos numéricos e string.
Você também pode criar suas próprias restrições:
// Restrição personalizada: o tipo deve ter um método String()type Stringer interface { String() string}
func Describe[T Stringer](v T) string { return "Value: " + v.String()}3. Structs e métodos genéricos
Os genéricos no Go funcionam não apenas com funções, mas também com tipos personalizados. Vejamos um exemplo clássico — uma pilha genérica:
package main
import "fmt"
// Struct genérica Stacktype Stack[T any] struct { items []T}
// Push adiciona um elemento à pilhafunc (s *Stack[T]) Push(item T) { s.items = append(s.items, item)}
// Pop remove um elemento da pilhafunc (s *Stack[T]) Pop() (T, bool) { if len(s.items) == 0 { var zero T return zero, false } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}
func main()
```