Type converting slices of interfaces

ghz 9months ago ⋅ 148 views

I'm curious why Go does't implicitly convert []T to []interface{} when it will implicitly convert T to interface{}. Is there something non-trivial about this conversion that I'm missing?

Example:

func foo([]interface{}) { /* do something */ }

func main() {
    var a []string = []string{"hello", "world"}
    foo(a)
}

go build complains

cannot use a (type []string) as type []interface {} in function argument

And if I try to do it explicitly, same thing: b := []interface{}(a) complains

cannot convert a (type []string) to type []interface {}

So every time I need to do this conversion (which seems to come up a lot), I've been doing something like this:

b = make([]interface{}, len(a), len(a))
for i := range a {
    b[i] = a[i]
}

Is there a better way to do this, or standard library functions to help with these conversions? It seems kind of silly to write 4 extra lines of code every time I want to call a function that can take a list of e.g. ints or strings.

Answers

The reason Go doesn't allow implicit conversion from []T to []interface{} is because Go's type system is statically typed and it aims to catch type errors at compile time. Allowing such implicit conversions would violate type safety because it would allow mixing different types in a slice.

While Go does allow implicit conversion from T to interface{} because every type implements the empty interface (interface{}), slices of different types are not directly compatible with each other.

Your approach of manually converting each element of the slice to interface{} is a common and idiomatic way to achieve what you want. However, you can simplify it slightly by using the built-in append function:

func main() {
    var a []string = []string{"hello", "world"}

    // Convert []string to []interface{}
    var b []interface{}
    for _, v := range a {
        b = append(b, v)
    }

    // Now you can pass b to functions expecting []interface{}
    foo(b)
}

Alternatively, if you find yourself needing to do this conversion frequently, you might consider creating a helper function to handle it:

func toInterfaceSlice(slice interface{}) []interface{} {
    v := reflect.ValueOf(slice)
    if v.Kind() != reflect.Slice {
        panic("toInterfaceSlice: not a slice")
    }
    result := make([]interface{}, v.Len())
    for i := 0; i < v.Len(); i++ {
        result[i] = v.Index(i).Interface()
    }
    return result
}

With this helper function, you can easily convert any slice to []interface{}:

var a []string = []string{"hello", "world"}
b := toInterfaceSlice(a)
foo(b)

But keep in mind that reflection introduces overhead and should be used judiciously, especially in performance-critical code.