Slices of Interfaces as Variadic Arguments

I’ve been playing around with my toy project, which involves building a collection of interfaces and quickly ran into the problem that a pretty normal-seeming use-case doesn’t work:

func NewCollection(list []Interface) { ... }

// later in another function
list := []ImplementsInterface{...}
c := NewCollection(list)

In other words, a slice of ImplementsInterface, being an unnamed composite type, isn’t the same type as a slice of Interface, a different unnamed composite type.

Fine, it made sense, so I naively set about manually allocating a new slice of Interface in the calling code, which worked but got ugly fast, like this:

list := []ImplementsInterface{ ... }
newlist := make([]Interface, len(list))

for i, l := range list {
    newlist[i] = Interface(l)
}
NewCollection(newlist)

Finally, after returning to code riddled with this type of stuff after a while, muttering curses and decided that there had to be a better way, I found out that changing the original argument to call not a slice, but a variadic argument, everything works the way it should:

func NewCollection(list ...Interface) { ... }

// later in another function
list := []ImplementsInterface{...}
c := NewCollection(list)

This is fantastic realization and changing function parameters to match this style should reduce boilerplate elsewhere, but I’m curious about a few things. From the language spec:

If f is variadic with a final parameter p of type …T, then within f the type of p is equivalent to type []T.

Apparently …T is equivalent to []T except that it allows for passing in implementers of T as detailed above, so my two big questions are:

  1. Is there any trap, side effect, or reason not to use variadics in this fashion?
  2. If …T is equivalent to []T, why can …T handle interface implementations appropriately, but []T cannot? I don’t excessively mind using …T in these case assuming the answer to (1) is “No.”, but I don’t understand the logic for the compiler not “figuring things out” in the case of []T just as simply as …T.

Finally, I this topic got away from me on length, but here’s a playground link with this behavior shown.

It’s a bit tricky, if you write a function

func f(v ...string)

Then the function will accept ANY parameter, including interface{}, and []interface{}. That is you can pass a slice, and it will be interpreted as a single parameter

var x []string
f(x) // x will be passed as v[0]

However, you want to unpack x into f, use the ... form.

f(x...)

See https://golang.org/ref/spec#VarDecl

Yikes, I should have known better than to use interface{} there. Still, I was sure I had this working previously. Argh.

Well, the broader question remains, what is the best way to handle passing slices of interface values around?

Do you mean slices of interface{} ?

When you are creating arrays you can make them the type of your interface instead of the implementer. I modified your playgroud https://play.golang.org/p/x5SKavm1fC take a look if this is what you were trying to accomplish.

What you are asking for is called covariance and it is not implemented in Go.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.