O parâmetro ...T
Coloque ... antes do tipo do último parâmetro. Quem chama pode então passar qualquer quantidade de argumentos daquele tipo, inclusive nenhum. Dentro da função, o parâmetro é um slice.
Isto imprime 0, 5 e 10. Sem argumentos, nums é um slice nil de tamanho 0, então o laço simplesmente não executa.
Parâmetros comuns podem vir antes. Só o último pode ser variádico:
func logf(level string, format string, args ...any)
Passando um slice com s...
Se você já tem um slice, não pode passá-lo diretamente: sum(nums) falha com cannot use nums (variable of type []int) as int value in argument to sum. Espalhe-o com ... depois do argumento:
O append também é variádico, e por isso juntar dois slices é append(a, b...).
O espalhamento precisa ser a única coisa na posição variádica. sum(1, nums...) falha com too many arguments in call to sum. Monte antes o slice combinado com append.
O slice é compartilhado, não copiado
Quando você chama com argumentos avulsos, o Go cria um slice novo para eles. Quando você espalha um slice existente com s..., a função recebe aquele mesmo slice. Escritas nos elementos dele alteram os dados de quem chama.
Saída:
[7 8 9]
[0 8 9]
Uma função variádica que modifica o seu parâmetro deveria documentar isso, ou copiar antes com slices.Clone. A maioria das funções variádicas só lê os argumentos, então isso raramente dá problema, mas é uma diferença real em relação a linguagens em que os parâmetros rest são sempre um array novo.
...any e como o fmt.Println funciona
fmt.Println é declarada como func Println(a ...any) (n int, err error). any (um alias de interface{} desde o Go 1.18) aceita qualquer tipo, então você pode passar strings, números e structs na mesma chamada.
O detalhe importante é o repasse: logf passa args... para o Printf. Sem os pontos, o Printf recebe um único argumento, um []any, e a linha de aviso imprime [WARN] [3 8] of %!d(MISSING) workers idle. O go vet só reporta missing ... in args forwarded to printf-like function quando o parâmetro de formato é repassado sem alteração, como em fmt.Printf(format, args). Aqui o formato é montado com +, então o vet fica quieto e os pontos são responsabilidade sua.
Um []string não pode ser espalhado em ...any. Os tipos dos elementos precisam bater exatamente, então converta antes:
names := []string{"a", "b"}
args := make([]any, len(names))
for i, n := range names {
args[i] = n
}
fmt.Println(args...)
Exigindo pelo menos um argumento
Um parâmetro variádico aceita zero argumentos. Quando a função precisa de pelo menos um, transforme o primeiro em parâmetro comum:
Agora maxOf() é erro de compilação em vez de uma verificação em tempo de execução. A própria função embutida max funciona assim: ela exige pelo menos um argumento.
Parâmetros variádicos como argumentos opcionais
Go não tem valores padrão para parâmetros. Às vezes um parâmetro variádico é usado para simular um argumento opcional:
func connect(addr string, timeout ...time.Duration)
Funciona, mas fica pouco legível, já que quem chama pode passar três timeouts e a assinatura não diz qual deles vale. Para mais de uma opção, prefira uma struct de configuração ou o padrão functional options (...Option, em que cada Option é uma função que define um campo). Esse padrão é variádico, mas cada argumento tem um significado claro.
Referência das regras
| Regra | Exemplo |
|---|---|
| Só o último parâmetro pode ser variádico | func f(a string, b ...int) |
| Dentro da função, o parâmetro é um slice | b tem tipo []int |
| Zero argumentos geram um slice nil | f("x") faz b == nil |
Espalhe um slice com ... | f("x", nums...) |
| O espalhamento compartilha o array subjacente | escritas em b[i] mudam nums |
| Sem misturar espalhamento com valores extras | f("x", 1, nums...) não compila |
Perguntas frequentes
O que é uma função variádica em Go?
Uma função cujo último parâmetro tem a forma ...T, como func sum(nums ...int) int. Ela pode ser chamada com zero ou mais argumentos do tipo T, e dentro da função o parâmetro é um slice []T.
Como passar um slice para uma função variádica em Go?
Acrescente ... depois do slice: sum(nums...). O slice é passado como está, sem cópia, então a função enxerga o mesmo array subjacente. Não dá para misturar o espalhamento com argumentos avulsos: sum(1, nums...) não compila.
Go tem operador spread?
Só para o último argumento de uma chamada variádica: f(s...). Não existe um spread geral para montar arrays, structs ou chamar funções não variádicas com um slice. append(a, b...) é a mesma regra aplicada ao append embutido.
Uma função Go pode ter dois parâmetros variádicos?
Não. Só o último parâmetro pode ser variádico, e só pode haver um por função. Passe listas extras como parâmetros comuns do tipo slice.