33

I'm wondering why it's not possible to do the following in go:

func main() {
    stuff := []string{"baz", "bla"}
    foo("bar", stuff...)
}

func foo(s ...string) {
    fmt.Println(s)
}

In my understanding, slice... "explodes" the slice so it can be used for multi argument function calls. So the above example should actually expand to foo("bar", "baz", "bla").

foo(stuff...) works as expected, no surprises here, but in the example above, the compiler complains about too many arguments.

Is this a desired limitation? I'm coming from a ruby background where a foo("bar", *stuff) is perfectly fine (and is, at least in my book, the same thing), that's why this surprises me.

icza
  • 389,944
  • 63
  • 907
  • 827
Pascal
  • 5,879
  • 2
  • 22
  • 34

4 Answers4

24

The value for a variadic argument can be specified either by enumerating the elements, or using an existing slice, specified by its name followed by ....

You want to mix the 2 possible ways which is not permitted by the Go Language Specification (Passing arguments to ... parameters).

If the first form is used (enumerating the elements):

The value passed [as the variadic parameter] is a new slice of type []T with a new underlying array whose successive elements are the actual arguments.

If the latter is used (passing an existing slice followed by ...) no new slice is created, the one you pass is used as is. And the passed slice can only be used to specify the value of one – the final – variadic parameter. Attempting to pass both a single element and a slice will not match the signature (the parameter list in this case) of your function and you'll get an error:

too many arguments in call to foo

There is no actual "exploding" involved in Go, the term is just used in other languages to help visualize that the passed array or slice will not be an element of the variadic parameter but will be the value of variadic parameter itself.

Mixing the 2 would require to allocate a new slice because obviously the existing slice cannot be used.

icza
  • 389,944
  • 63
  • 907
  • 827
22

The ugly way to get this to work is make it into a new variadic.

foo(append([]string{"bar"}, stuff...)...)

And if the order doesn't matter:

foo(append(stuff, "bar")...)

https://play.golang.org/p/mY6y0vScfPB

Community
  • 1
  • 1
mvndaai
  • 3,453
  • 3
  • 30
  • 34
  • 1
    Please note, that the second version might introduce unexpected side effects, when the capacity of the `stuff` slice is larger than its size: https://go.dev/play/p/y-j9SQcr7KA – Ska May 09 '23 at 09:48
5

The specification on this is at the "Passing arguments to ... parameters":

If f is variadic with a final parameter p of type ...T, then within f the type of p is equivalent to type []T.
If f is invoked with no actual arguments for p, the value passed to p is nil.
Otherwise, the value passed is a new slice of type []T with a new underlying array whose successive elements are the actual arguments, which all must be assignable to T.

In your case, where stuff... works:

If the final argument is assignable to a slice type []T, it may be passed unchanged as the value for a ...T parameter if the argument is followed by .... In this case no new slice is created.

But "bar", stuff... doesn't match either case specified above.

T, []T doesn't match f([]T).

Community
  • 1
  • 1
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
1

I ran into this situation when preparing arguments to feed to external commands. If possible, just build an one argument slice, then you don't have to worry about combining scalars with slices when it's time to call the function:

package main
import "os/exec"

func main() {
   stuff := []string{"bar"}
   stuff = append(stuff, "baz", "bla")
   exec.Command("name", stuff...).Run()
}
halfer
  • 19,824
  • 17
  • 99
  • 186
Zombo
  • 1
  • 62
  • 391
  • 407