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