<h2>Introdução aos Generics em Go</h2>
<p>Generics é um recurso que chegou ao Go na versão 1.18, permitindo que você escreva código mais flexível e reutilizável sem perder a segurança de tipos. Antes disso, Go era conhecida por ser uma linguagem simples, sem suporte a tipos genéricos, o que frequentemente levava ao uso de <code>interface{}</code> e type assertions — uma abordagem que funcionava, mas era propensa a erros em tempo de execução.</p>
<p>O impacto de adicionar generics ao Go foi significativo: agora você pode escrever funções, tipos e métodos que trabalham com múltiplos tipos de dados mantendo a segurança de tipos em tempo de compilação. Isso reduz duplicação de código, elimina type assertions desnecessários e melhora a legibilidade. Neste artigo, você entenderá como usar type parameters e constraints de forma prática, e verá casos reais onde generics fazem toda diferença.</p>
<h2>Type Parameters: O Conceito Fundamental</h2>
<p>Type parameters são placeholders para tipos que serão preenchidos quando você usa a função ou tipo genérico. Eles são declarados entre colchetes angulares <code>[]</code> e funcionam de forma semelhante a linguagens como Java e TypeScript, mas com a simplicidade que Go sempre promoveu.</p>
<h3>Sintaxe Básica</h3>
<p>A sintaxe para declarar um type parameter é simples. Em uma função genérica, você coloca o nome do tipo entre colchetes após o nome da função:</p>
<pre><code class="language-go">func Primeiro[T any](slice []T) T {
if len(slice) == 0 {
var zero T
return zero
}
return slice[0]
}</code></pre>
<p>Aqui, <code>T</code> é o type parameter, e <code>any</code> é sua constraint (falaremos sobre isso na próxima seção). A função <code>Primeiro</code> aceita um slice de qualquer tipo e retorna o primeiro elemento. Você chama essa função passando o tipo explicitamente ou deixando o compilador inferir:</p>
<pre><code class="language-go">// Explícito
numeros := []int{1, 2, 3}
primeiro := Primeiro[int](numeros) // primeiro = 1
// Inferido
palavras := []string{"hello", "world"}
palavra := Primeiro(palavras) // Go infere string automaticamente</code></pre>
<h3>Type Parameters em Tipos Personalizados</h3>
<p>Você também pode usar type parameters ao definir tipos (structs, interfaces, tipos base):</p>
<pre><code class="language-go">type Pilha[T any] struct {
elementos []T
}
func (p *Pilha[T]) Empilhar(elemento T) {
p.elementos = append(p.elementos, elemento)
}
func (p *Pilha[T]) Desempilhar() (T, bool) {
var zero T
if len(p.elementos) == 0 {
return zero, false
}
ultimo := p.elementos[len(p.elementos)-1]
p.elementos = p.elementos[:len(p.elementos)-1]
return ultimo, true
}</code></pre>
<p>Você instancia essa pilha para um tipo específico:</p>
<pre><code class="language-go">pilha := &Pilha[string]{}
pilha.Empilhar("Alice")
pilha.Empilhar("Bob")
nome, ok := pilha.Desempilhar() // nome = "Bob", ok = true</code></pre>
<h2>Constraints: Definindo Limites ao Polimorfismo</h2>
<p>Constraints definem quais tipos podem ser usados para um type parameter. Sem constraints, você está limitado a operações muito básicas (atribuição, passagem para funções como argumento). Constraints permitem assumir propriedades específicas dos tipos que você está trabalhando.</p>
<h3>Constraint <code>any</code></h3>
<p>A constraint <code>any</code> é equivalente a <code>interface{}</code> — permite qualquer tipo. É útil quando você não precisa de operações específicas no type parameter:</p>
<pre><code class="language-go">func Imprimir[T any](valor T) {
fmt.Println(valor)
}
Imprimir(42)
Imprimir("hello")
Imprimir(3.14)</code></pre>
<h3>Constraints Baseadas em Tipos Específicos</h3>
<p>Você pode especificar que um type parameter deve ser um dos tipos concretos listados:</p>
<pre><code class="language-go">func Somar[T int | int64 | float64](a, b T) T {
return a + b
}
resultado1 := Somar(5, 3) // int: 8
resultado2 := Somar(int64(10), 5) // int64: 15
resultado3 := Somar(2.5, 3.7) // float64: 6.2</code></pre>
<p>O operador <code>|</code> na constraint significa "ou", permitindo múltiplos tipos.</p>
<h3>Constraints com Interfaces</h3>
<p>A abordagem mais poderosa é usar uma interface como constraint. Você define uma interface com os métodos ou características que o type parameter deve ter:</p>
<pre><code class="language-go">type Comparable interface {
Comparar(outro Comparable) int
}
func Maximo[T Comparable](a, b T) T {
if a.Comparar(b) > 0 {
return a
}
return b
}</code></pre>
<p>Qualquer tipo que implemente a interface <code>Comparable</code> pode ser usado:</p>
<pre><code class="language-go">type Numero int
func (n Numero) Comparar(outro Comparable) int {
o := outro.(Numero)
if n > o {
return 1
} else if n < o {
return -1
}
return 0
}
max := Maximo(Numero(10), Numero(5)) // Numero(10)</code></pre>
<h3>Constraints Pré-definidas (Pacote <code>constraints</code>)</h3>
<p>Go fornece algumas constraints prontas no pacote <code>constraints</code>:</p>
<pre><code class="language-go">import "golang.org/x/exp/constraints"
func MaiorNumero[T constraints.Ordered](a, b T) T {
if a > b {
return a
}
return b
}
resultado := MaiorNumero(7, 3) // 7
resultado2 := MaiorNumero("zebra", "apple") // "zebra"</code></pre>
<p>A constraint <code>constraints.Ordered</code> funciona com tipos que suportam operadores de comparação (<code><</code>, <code>></code>, <code>==</code>, etc.).</p>
<h2>Casos de Uso Reais</h2>
<p>Generics resolvem problemas práticos do dia a dia. Vamos explorar cenários onde eles realmente fazem diferença.</p>
<h3>Caso 1: Estruturas de Dados Genéricas</h3>
<p>Antes de generics, implementar uma fila ou lista reutilizável era complicado. Agora:</p>
<pre><code class="language-go">type Fila[T any] struct {
items []T
}
func (f *Fila[T]) Enfileirar(item T) {
f.items = append(f.items, item)
}
func (f *Fila[T]) Desenfileirar() (T, bool) {
var zero T
if len(f.items) == 0 {
return zero, false
}
item := f.items[0]
f.items = f.items[1:]
return item, true
}
// Uso
filaDePessoas := &Fila[string]{}
filaDePessoas.Enfileirar("Alice")
filaDePessoas.Enfileirar("Bob")
pessoa, ok := filaDePessoas.Desenfileirar() // "Alice"</code></pre>
<h3>Caso 2: Funções Utilitárias em Slices</h3>
<p>Operações comuns com slices agora são genéricas:</p>
<pre><code class="language-go">func Filtrar[T any](items []T, predicado func(T) bool) []T {
resultado := []T{}
for _, item := range items {
if predicado(item) {
resultado = append(resultado, item)
}
}
return resultado
}
numeros := []int{1, 2, 3, 4, 5}
pares := Filtrar(numeros, func(n int) bool { return n%2 == 0 })
// pares = []int{2, 4}</code></pre>
<h3>Caso 3: API HTTP com Respostas Genéricas</h3>
<p>Um padrão muito comum é uma resposta JSON genérica:</p>
<pre><code class="language-go">type Resposta[T any] struct {
Sucesso bool json:"sucesso"
Dados T json:"dados"
Erro string json:"erro,omitempty"
}
type Usuario struct {
ID int json:"id"
Nome string json:"nome"
}
func ObterUsuario(w http.ResponseWriter, r *http.Request) {
usuario := Usuario{ID: 1, Nome: "Alice"}
resposta := Resposta[Usuario]{
Sucesso: true,
Dados: usuario,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resposta)
}</code></pre>
<h3>Caso 4: Cache Genérico</h3>
<p>Um cache que funciona com qualquer tipo de dado:</p>
<pre><code class="language-go">type Cache[T any] struct {
dados map[string]T
mu sync.RWMutex
}
func (c *Cache[T]) Set(chave string, valor T) {
c.mu.Lock()
defer c.mu.Unlock()
if c.dados == nil {
c.dados = make(map[string]T)
}
c.dados[chave] = valor
}
func (c *Cache[T]) Get(chave string) (T, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
valor, existe := c.dados[chave]
return valor, existe
}
// Uso
cacheUsuarios := &Cache[Usuario]{}
cacheUsuarios.Set("user:1", Usuario{ID: 1, Nome: "Alice"})
usuario, ok := cacheUsuarios.Get("user:1")</code></pre>
<h3>Caso 5: Constraint com Múltiplos Métodos</h3>
<p>Às vezes você precisa que um type parameter implemente vários métodos:</p>
<pre><code class="language-go">type Serializavel interface {
Marshal() ([]byte, error)
Unmarshal([]byte) error
}
func ProcessarDados[T Serializavel](dados T) ([]byte, error) {
return dados.Marshal()
}</code></pre>
<h2>Boas Práticas e Limitações</h2>
<p>Generics são poderosos, mas exigem moderação. Use generics quando eles reduzirem duplicação de código de verdade. Não use quando tornar o código mais complexo sem benefício claro.</p>
<h3>Quando NÃO usar Generics</h3>
<ul>
<li>Quando uma solução com <code>interface{}</code> é suficiente e clara</li>
<li>Quando a lógica depende muito do tipo específico (melhor é duplicar código)</li>
<li>Em tipos com apenas alguns casos específicos</li>
</ul>
<h3>Quando USAR Generics</h3>
<ul>
<li>Estruturas de dados que armazenam múltiplos tipos (pilhas, filas, árvores)</li>
<li>Funções utilitárias que operam em slices ou maps de qualquer tipo</li>
<li>APIs que retornam respostas genéricas</li>
<li>Quando você vê duplicação de código idêntico com tipos diferentes</li>
</ul>
<h2>Conclusão</h2>
<p>Generics em Go trouxe três melhorias fundamentais: <strong>reutilização de código sem sacrificar segurança de tipos</strong>, <strong>eliminação de type assertions desnecessários</strong> e <strong>clareza na intenção do código</strong> (quando bem utilizados). Type parameters e constraints funcionam juntos para permitir polimorfismo seguro. Na prática, você usará generics principalmente em estruturas de dados, funções utilitárias e padrões de API genérica — mas sempre com moderação, preferindo simplicidade quando possível.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://go.dev/doc/tutorial/generics" target="_blank" rel="noopener noreferrer">Documentação Oficial - Type Parameters</a></li>
<li><a href="https://github.com/golang/proposal/blob/master/design/43651-type-parameters.md" target="_blank" rel="noopener noreferrer">Go Proposal: Generics - Design Document</a></li>
<li><a href="https://pkg.go.dev/golang.org/x/exp/constraints" target="_blank" rel="noopener noreferrer">Pacote constraints - golang.org/x/exp</a></li>
<li><a href="https://go.dev/doc/effective_go" target="_blank" rel="noopener noreferrer">The Go Programming Language - Effective Go</a></li>
<li><a href="https://go.dev/tour/generics/1" target="_blank" rel="noopener noreferrer">A Tour of Go - Generics</a></li>
</ul>
<p><!-- FIM --></p>