Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type converting slices of interfaces

In Go, there is a general rule that syntax should not hide complex/costly operations. Converting a string to an interface{} is done in O(1) time. Converting a []string to an interface{} is also done in O(1) time since a slice is still one value. However, converting a []string to an []interface{} is O(n) time because each element of the slice must be converted to an interface{}.

The one exception to this rule is converting strings. When converting a string to and from a []byte or a []rune, Go does O(n) work even though conversions are "syntax".

There is no standard library function that will do this conversion for you. You could make one with reflect, but it would be slower than the three line option.

Example with reflection:

func InterfaceSlice(slice interface{}) []interface{} {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("InterfaceSlice() given a non-slice type")
    }

    // Keep the distinction between nil and empty slice input
    if s.IsNil() {
        return nil
    }

    ret := make([]interface{}, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = s.Index(i).Interface()
    }

    return ret
}

Your best option though is just to use the lines of code you gave in your question:

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

The thing you are missing is that T and interface{} which holds a value of T have different representations in memory so can't be trivially converted.

A variable of type T is just its value in memory. There is no associated type information (in Go every variable has a single type known at compile time not at run time). It is represented in memory like this:

  • value

An interface{} holding a variable of type T is represented in memory like this

  • pointer to type T
  • value

So coming back to your original question: why go does't implicitly convert []T to []interface{}?

Converting []T to []interface{} would involve creating a new slice of interface {} values which is a non-trivial operation since the in-memory layout is completely different.


Here is the official explanation: https://github.com/golang/go/wiki/InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d
}

Try interface{} instead. To cast back as slice, try

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}

In case you need more shorting your code, you can creating new type for helper

type Strings []string

func (ss Strings) ToInterfaceSlice() []interface{} {
    iface := make([]interface{}, len(ss))
    for i := range ss {
        iface[i] = ss[i]
    }
    return iface
}

then

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()

I was curious how much slower it is convert interface arrays via reflection vs. doing it inside a loop, as described in Stephen's answer. Here's a benchmark comparison of the two approaches:

benchmark                             iter      time/iter   bytes alloc         allocs
---------                             ----      ---------   -----------         ------
BenchmarkLoopConversion-12         2285820   522.30 ns/op      400 B/op   11 allocs/op
BenchmarkReflectionConversion-12   1780002   669.00 ns/op      584 B/op   13 allocs/op

So using a loop is ~20% faster than doing it via reflection.

Here's my test code in case you'd like to verify if I did things correctly:

    import (
        "math/rand"
        "reflect"
        "testing"
        "time"
    )
    
    func InterfaceSlice(slice interface{}) []interface{} {
        s := reflect.ValueOf(slice)
        if s.Kind() != reflect.Slice {
            panic("InterfaceSlice() given a non-slice type")
        }
    
        // Keep the distinction between nil and empty slice input
        if s.IsNil() {
            return nil
        }
    
        ret := make([]interface{}, s.Len())
    
        for i := 0; i < s.Len(); i++ {
            ret[i] = s.Index(i).Interface()
        }
    
        return ret
    }
    
    type TestStruct struct {
        name string
        age  int
    }
    
    var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    
    func randSeq(n int) string {
        b := make([]rune, n)
        for i := range b {
            b[i] = letters[rand.Intn(len(letters))]
        }
        return string(b)
    }
    
    func randTestStruct(lenArray int, lenMap int) map[int][]TestStruct {
        randomStructMap := make(map[int][]TestStruct, lenMap)
        for i := 0; i < lenMap; i++ {
            var testStructs = make([]TestStruct, 0)
            for k := 0; k < lenArray; k++ {
                rand.Seed(time.Now().UnixNano())
                randomString := randSeq(10)
                randomInt := rand.Intn(100)
                testStructs = append(testStructs, TestStruct{name: randomString, age: randomInt})
            }
            randomStructMap[i] = testStructs
        }
        return randomStructMap
    }
    
    func BenchmarkLoopConversion(b *testing.B) {
        var testStructMap = randTestStruct(10, 100)
        b.ResetTimer()
    
        for i := 0; i < b.N; i++ {
            obj := make([]interface{}, len(testStructMap[i%100]))
            for k := range testStructMap[i%100] {
                obj[k] = testStructMap[i%100][k]
            }
        }
    }
    
    func BenchmarkReflectionConversion(b *testing.B) {
        var testStructMap = randTestStruct(10, 100)
        b.ResetTimer()
    
        for i := 0; i < b.N; i++ {
            obj := make([]interface{}, len(testStructMap[i%100]))
            obj = InterfaceSlice(testStructMap[i%100])
            _ = obj
        }
    }