Type converting slices of interfaces

GoGo ReflectGo Interface

Go Problem Overview


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.

Go Solutions


Solution 1 - Go

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. 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]
}

Otherwise, 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
}

Solution 2 - Go

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.

Solution 3 - Go

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
}

Solution 4 - Go

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

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

Solution 5 - Go

In Go 1.18 or later, use the following function to convert an arbitrary slice type to []interface{} or its alias any:

func ToSliceOfAny[T any](s []T) []any {
	result := make([]any, len(s))
	for i, v := range s {
		result[i] = v
	}
	return result
}

The Go 1.18 generics feature does not eliminate the need to convert an arbitrary slice to []any. Here's an example of where the conversion is required: The application wants to query a database using the elements of a []string as the variadic query arguments declared as args ...any. The function in this answer allows the application to query the database in a convenient one-liner:

rows, err := db.Query(qs, ToSliceOfAny(stringArgs)...)

Solution 6 - Go

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()

Solution 7 - Go

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
    	}
    }

Solution 8 - Go

Convert interface{} into any type.

Syntax:

result := interface.(datatype)

Example:

var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string)   //result type is []string.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondannyView Question on Stackoverflow
Solution 1 - GoStephen WeinbergView Answer on Stackoverflow
Solution 2 - GoNick Craig-WoodView Answer on Stackoverflow
Solution 3 - GoYandry PozoView Answer on Stackoverflow
Solution 4 - GodskinnerView Answer on Stackoverflow
Solution 5 - GoZomboView Answer on Stackoverflow
Solution 6 - GoRaditzLawlietView Answer on Stackoverflow
Solution 7 - GoJonathanReezView Answer on Stackoverflow
Solution 8 - Goyala rameshView Answer on Stackoverflow