Funções Variádicas em Go
1. O que são Funções Variádicas?
Funções variádicas são funções que podem receber um número variável de argumentos de um determinado tipo. Em Go, isso é declarado usando a sintaxe ...Tipo antes do nome do parâmetro. O exemplo mais famoso e amplamente utilizado é fmt.Println, que aceita qualquer quantidade de argumentos:
fmt.Println("olá", "mundo", 42, true)
Internamente, o compilador Go trata o parâmetro variádico como um slice. Quando você chama uma função variádica, os argumentos passados são empacotados em um slice desse tipo. Se nenhum argumento for passado, o slice será vazio (nil ou []T{}).
package main
import "fmt"
func main() {
// Ambos funcionam
fmt.Println("um argumento")
fmt.Println("vários", "argumentos", "aqui")
fmt.Println() // zero argumentos
}
2. Declaração e Uso Básico
Para declarar uma função variádica, use ... antes do tipo do último parâmetro:
func soma(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(soma(1, 2)) // 3
fmt.Println(soma(1, 2, 3, 4)) // 10
fmt.Println(soma()) // 0
}
Dentro da função, nums se comporta exatamente como um []int. Você pode iterar sobre ele, acessar índices, usar len() e cap(), e aplicar qualquer operação de slice.
3. Slice como Argumento Variádico
Uma característica poderosa é a capacidade de "explodir" um slice existente ao chamar uma função variádica, usando a sintaxe slice...:
func main() {
numeros := []int{10, 20, 30, 40}
resultado := soma(numeros...) // equivalente a soma(10, 20, 30, 40)
fmt.Println(resultado) // 100
}
Isso é útil para concatenar slices dinamicamente:
func concat(slices ...[]int) []int {
var result []int
for _, s := range slices {
result = append(result, s...)
}
return result
}
func main() {
a := []int{1, 2}
b := []int{3, 4}
c := []int{5, 6}
todos := concat(a, b, c)
fmt.Println(todos) // [1 2 3 4 5 6]
}
A diferença entre nums []int e nums ...int é fundamental: no primeiro caso, você obriga o chamador a criar um slice; no segundo, ele pode passar argumentos avulsos ou um slice com ....
4. Combinando Parâmetros Fixos e Variádicos
Uma regra importante: o parâmetro variádico deve ser o último na assinatura da função:
// Correto
func log(prefix string, messages ...string) {
for _, msg := range messages {
fmt.Printf("[%s] %s\n", prefix, msg)
}
}
func main() {
log("INFO", "servidor iniciado", "conexão estabelecida")
log("ERRO", "falha ao conectar")
log("DEBUG") // apenas prefixo, sem mensagens
}
Um erro comum é colocar o parâmetro variádico antes de parâmetros fixos:
// INCORRETO - não compila
func errado(messages ...string, prefix string) {
// ...
}
Isso gera erro de compilação porque o compilador não saberia onde termina o variádico e começam os parâmetros fixos.
5. Funções Variádicas com Tipos Genéricos (Go 1.18+)
Com a introdução de genéricos no Go 1.18, podemos criar funções variádicas que aceitam múltiplos tipos diferentes usando any:
func printAll(values ...any) {
for _, v := range values {
fmt.Printf("%v (tipo: %T)\n", v, v)
}
}
func main() {
printAll(42, "texto", 3.14, true)
}
Para casos mais avançados, podemos usar type parameters com variádicos:
func Map[T any](fn func(T) T, values ...T) []T {
result := make([]T, len(values))
for i, v := range values {
result[i] = fn(v)
}
return result
}
func main() {
dobrados := Map(func(x int) int { return x * 2 }, 1, 2, 3, 4)
fmt.Println(dobrados) // [2 4 6 8]
maiusculas := Map(func(s string) string { return strings.ToUpper(s) }, "go", "lang")
fmt.Println(maiusculas) // [GO LANG]
}
6. Boas Práticas e Armadilhas Comuns
Evite modificar o slice subjacente: Lembre-se de que o slice variádico compartilha o array subjacente com o slice original (quando passado com ...). Modificá-lo pode causar efeitos colaterais inesperados:
func modificar(nums ...int) {
nums[0] = 999 // Isso modifica o array original!
}
func main() {
original := []int{1, 2, 3}
modificar(original...)
fmt.Println(original) // [999 2 3] - surpresa!
}
Performance: Quando você usa ... para passar um slice, nenhuma cópia é feita — apenas a referência ao slice é passada. Porém, se você passar argumentos avulsos, o compilador cria um novo slice internamente.
Quando usar variádicos vs slices explícitos: Use variádicos quando a API se beneficiar de chamadas mais concisas e legíveis. Para funções internas ou quando o número de argumentos é sempre grande, prefira slices explícitos.
7. Casos de Uso Avançados
Logging e debugging:
type Logger struct {
prefix string
}
func (l *Logger) Log(level string, messages ...any) {
for _, msg := range messages {
fmt.Printf("[%s] [%s] %v\n", l.prefix, level, msg)
}
}
func main() {
log := Logger{prefix: "APP"}
log.Log("INFO", "iniciando", "versão 2.0")
log.Log("ERRO", "falha crítica", 500, "timeout")
}
Middleware com argumentos variáveis:
func chain(handlers ...func(string)) func(string) {
return func(msg string) {
for _, h := range handlers {
h(msg)
}
}
}
func main() {
h := chain(
func(s string) { fmt.Println("Handler 1:", s) },
func(s string) { fmt.Println("Handler 2:", s) },
)
h("teste")
}
8. Comparação com Outras Abordagens
Variádicos vs Sobrecarga: Go não suporta sobrecarga de funções. Funções variádicas são a alternativa Go para lidar com número variável de argumentos, evitando a complexidade da sobrecarga.
Variádicos vs Interface + Type Switch: Para argumentos de tipos completamente diferentes, você pode usar ...any combinado com type switch:
func process(values ...any) {
for _, v := range values {
switch val := v.(type) {
case int:
fmt.Println("Inteiro:", val*2)
case string:
fmt.Println("String:", strings.ToUpper(val))
default:
fmt.Println("Desconhecido:", val)
}
}
}
Quando optar por variádicos em APIs públicas: Use variádicos quando a maioria das chamadas usar poucos argumentos e a legibilidade for importante. Evite em funções de alto desempenho ou quando o número de argumentos for tipicamente grande.
Referências
- Documentação oficial de Go: Funções variádicas — Especificação completa da linguagem sobre parâmetros variádicos
- Effective Go: Variadic functions — Seção do guia Effective Go abordando boas práticas com funções variádicas
- Go by Example: Variadic Functions — Exemplos práticos e concisos de funções variádicas
- Tutorial da DigitalOcean: Understanding Variadic Functions in Go — Tutorial completo com casos de uso do mundo real
- The Go Blog: Generics in Go 1.18 — Artigo oficial sobre genéricos, incluindo exemplos com parâmetros variádicos tipados
- Stack Overflow: How to pass a slice to variadic function in Go — Discussão técnica sobre o comportamento de slices com
... - Medium: Variadic Functions in Go - Common Pitfalls — Análise de armadilhas e erros frequentes ao usar funções variádicas