Generics em Go: para que servem e como usar
Com o lançamento da versão Go 1.18, uma das funcionalidades mais esperadas apareceu na linguagem — os genéricos (programação genérica). Até este momento, os desenvolvedores tinham que duplicar código para diferentes tipos de dados ou usar a interface vazia interface{} com posterior type assertion. Os Generics resolvem este problema radicalmente. Neste artigo, vamos analisar para que servem, como usá-los na prática e quais armadilhas esperam por você.
O que são genéricos e para que servem em Go?
Genéricos (ou tipos genéricos) permitem escrever funções, estruturas e métodos que funcionam com qualquer tipo de dado, mantendo ao mesmo tempo a tipagem estrita. Isso significa que você pode escrever uma única função que funcionará tanto com números, quanto com strings, quanto com tipos personalizados, sem perder a informação sobre o tipo em tempo de compilação.
Antes do surgimento dos genéricos, os desenvolvedores Go usavam duas abordagens principais:
- Duplicação de código — escrever funções separadas para
int,string,float64, etc. - Interface vazia — usar
interface{}com posterior type assertion ou reflexão, o que era inseguro e lento.
Os genéricos resolvem estes problemas, permitindo escrever código universal, mas ao mesmo tempo type-safe. O compilador verifica os tipos em tempo de compilação, o que elimina erros em tempo de execução.
Sintaxe dos Generics: type parameters
A ideia principal dos genéricos em Go são os type parameters. Eles são declarados entre colchetes após o nome da função ou tipo.
Vamos considerar um exemplo simples — uma função que retorna o mínimo de dois valores:
package main
import "fmt"
// Função genérica com type parameter Tfunc Min[T comparable](a, b T) T { if a < b { return a } return b}
func main() { fmt.Println(Min[int](10, 20)) // 10 fmt.Println(Min[string]("a", "b")) // "a" fmt.Println(Min(3.14, 2.71)) // 2.71 (tipo inferido automaticamente)}Aqui T é o type parameter. comparable é uma constraint que diz que o tipo T deve suportar operadores de comparação. Sem esta constraint, o compilador não permitiria usar <.
Importante: Go pode inferir automaticamente os type parameters a partir dos argumentos, portanto na maioria dos casos você não precisa especificá-los explicitamente.
Constraints em genéricos Go
Constraints são um elemento chave dos genéricos. Elas definem quais operações são permitidas para os type parameters. Sem constraints, o compilador não sabe quais métodos ou operadores podem ser aplicados a T.
Em Go existem várias constraints embutidas:
any— sinônimo deinterface{}, permite qualquer tipo.comparable— permite tipos que suportam==e!=.constraints.Ordered— do pacotegolang.org/x/exp/constraints, permite todos os tipos ordenados (números e strings).
Você também pode criar suas próprias constraints usando interfaces:
package main
import "fmt"
// Constraint personalizada: o tipo deve ter o método String()type Stringer interface { String() string}
// Função que trabalha com qualquer Stringerfunc Print[T Stringer](value T) { fmt.Println(value.String())}
type User struct { Name string}
func (u User) String() string { return "User: " + u.Name}
func main() { user := User{Name: "Alice"} Print(user) // User: Alice}Observe: em Go 1.18+, interfaces podem ser usadas como constraints, e elas podem conter não apenas métodos, mas também tipos concretos.