Generics
Os Generics são um recurso que permite criar funções e estruturas capazes de trabalhar com diferentes tipos de dados, sem precisar escrever uma implementação separada para cada tipo. Esse recurso foi adicionado ao Go na versão 1.18 e é útil quando precisamos executar a mesma operação com tipos diferentes, mantendo a verificação de tipos feita pelo compilador.
Sem Generics, poderíamos fazer assim:
func SomarInt(a, b int) int {
return a + b
}
func SomarFloat(a, b float64) float64 {
return a + b
}
As duas funções fazem exatamente a mesma operação, mas precisamos repetir o código porque recebem tipos diferentes. Com Generics, podemos criar uma única função que aceita os dois tipos, assim:
package main
import "fmt"
// T representa um tipo que pode ser int ou float64.
func Somar[T int | float64](a, b T) T {
return a + b
}
func main() {
// Soma números inteiros.
fmt.Println(Somar(10, 20))
// Soma números decimais.
fmt.Println(Somar(1.5, 2.5))
}
Resultado:
30
4
Vamos entender esta parte:
func Somar[T int | float64]
O T representa um parâmetro de tipo, que permite definir qual tipo será usado pela função. Já int | float64 representa uma restrição, indicando que essa função aceita apenas valores desses dois tipos.
Vamos entender esta parte:
(a, b T) T
Em Go, quando declaramos uma função, precisamos definir os parâmetros que ela recebe e o tipo de valor que será retornado. Com Generics, o funcionamento é o mesmo, mas podemos usar um parâmetro de tipo, como T, no lugar de um tipo específico. O primeiro T indica que os parâmetros a e b possuem o mesmo tipo, enquanto o segundo T, depois dos parênteses, indica que a função retorna um valor desse mesmo tipo.
Isso significa que, se passarmos dois valores int, o retorno também será int. Se passarmos dois valores float64, o retorno será float64. Também precisamos lembrar que T representa um único tipo por chamada da função, então não podemos passar um int e um float64 ao mesmo tempo sem fazer uma conversão ou informar um tipo compatível.
O nome T é apenas uma convenção. Poderíamos usar outro nome, mas T é bastante comum para representar parâmetros de tipo em funções genéricas.
Generics com any
Também podemos usar any quando queremos que uma função aceite valores de qualquer tipo.
func Mostrar[T any](valor T) {
fmt.Println(valor)
}
Nesse caso, podemos passar números, textos ou outros tipos.
Mostrar(10)
Mostrar("Olá")
Mostrar(true)
Porém, usar any não significa que podemos executar qualquer operação com o valor, por exemplo, não podemos fazer a + b com um T any, porque nem todos os tipos aceitos por any permitem essa operação.
Os Generics fazem mais sentido quando precisamos repetir a mesma lógica para diferentes tipos de dados. Porém, quando uma função trabalha com apenas um tipo conhecido, normalmente é mais simples continuar usando esse tipo diretamente, sem adicionar Generics desnecessariamente.