In Go, if the function argument is interface{}, it can be called with any argument, and then converted by type assertion.

As an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import "fmt"

func foo(v interface{}) {
    if v1, ok1 := v.(string); ok1 {
        fmt.Println(v1)
    } else if v2, ok2 := v.(int); ok2 {
        fmt.Println(v2)
    }
}

func main() {
    foo(233)
    foo("666")
}

It doesn’t matter if you pass int or string, you will end up with the correct result.

So, since this is the case, I have a question, is it possible to convert []T to []interface?

For example, the following code.

1
2
3
4
5
6
func foo([]interface{}) { /* do something */ }

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

Unfortunately, this code does not compile, and if you try to convert it directly via b := []interface{}(a), it still reports an error.

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

The correct way to convert needs to be written like this.

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

Isn’t it a pain to have to write four lines of code when you could have done it in one line? Then why doesn’t Go support it? Let’s read on.

Official explanation

This question is answered in the official Wiki, which I have copied and placed below.

The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear. Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time. Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N*2 words long. This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be N*sizeof(MyType) words long. The result is that you cannot quickly assign something of type []MyType to something of type []interface{} ; the data behind them just look different.

Presumably, this means that there are two main reasons.

  1. the type []interface{} is not an interface, it is a slice, except that its elements are of type interface.
  2. []interface{} has a special memory layout, which is different from interface.

Here’s a detailed explanation of how it’s different.

Memory layout

First let’s see how slice is stored in memory. In the source code, it is defined like this.

1
2
3
4
5
6
7
// src/runtime/slice.go

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}
  • array is a pointer to the underlying array.
  • len is the length of the slice.
  • cap is the capacity of the slice, which is the size of the array array.

As an example, create a slice as follows.

1
is := []int64{0x55, 0x22, 0xab, 0x9}

Then its layout is shown in the following figure.

Memory layout of slices

Assuming the program is running on a 64-bit machine, each ‘square’ takes up 8 bytes of space, and the underlying array pointed to by ptr in the figure above takes up 4 squares, or 32 bytes.

Next, let’s see what []interface{} looks like in memory.

Before answering this question, let’s take a look at the structure of interface{}. The interface types in Go are divided into two categories.

  1. iface denotes interfaces that contain methods.
  2. eface denotes an empty interface that does not contain methods.

The definitions in the source code are as follows, respectively.

1
2
3
4
type iface struct {
    tab  *itab
    data unsafe.Pointer
}
1
2
3
4
type eface struct {
    _type *_type
    data  unsafe.Pointer
}

Without going into the details, it is clear that each interface{} contains two pointers, which occupy two “squares”. The first pointer points to itab or _type; the second pointer points to the actual data.

So its layout in memory is shown in the following figure.

Memory layout of slices

Therefore, you cannot pass []int64 directly to []interface{}.

Memory layout in program runtime

Next, let’s take a more visual approach and see how the memory distribution looks like from the actual running of the program.

Look at a piece of code like the following.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

var sum int64

func addUpDirect(s []int64) {
	for i := 0; i < len(s); i++ {
		sum += s[i]
	}
}

func addUpViaInterface(s []interface{}) {
	for i := 0; i < len(s); i++ {
		sum += s[i].(int64)
	}
}

func main() {
	is := []int64{0x55, 0x22, 0xab, 0x9}

	addUpDirect(is)

	iis := make([]interface{}, len(is))
	for i := 0; i < len(is); i++ {
		iis[i] = is[i]
	}

	addUpViaInterface(iis)
}

We use Delve for debugging, which can be installed by clicking here.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
dlv debug slice-layout.go
Type 'help' for list of commands.
(dlv) break slice-layout.go:27
Breakpoint 1 set at 0x105a3fe for main.main() ./slice-layout.go:27
(dlv) c
> main.main() ./slice-layout.go:27 (hits goroutine(1):1 total:1) (PC: 0x105a3fe)
    22:		iis := make([]interface{}, len(is))
    23:		for i := 0; i < len(is); i++ {
    24:			iis[i] = is[i]
    25:		}
    26:
=>  27:		addUpViaInterface(iis)
    28:	}

Print the address of is.

1
2
(dlv) p &is
(*[]int64)(0xc00003a740)

Next, see what the slice contains in memory.

1
2
3
4
5
(dlv) x -fmt hex -len 32 0xc00003a740
0xc00003a740:   0x10   0xa7   0x03   0x00   0xc0   0x00   0x00   0x00
0xc00003a748:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a750:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a758:   0x00   0x00   0x09   0x00   0xc0   0x00   0x00   0x00

Each line has 8 bytes, which is a “square” as mentioned above. The first line is the address of the data pointed to; the second line is 4, the slice length; and the third line is also 4, the slice capacity.

Let’s see how the data is stored.

1
2
3
4
5
(dlv) x -fmt hex -len 32 0xc00003a710
0xc00003a710:   0x55   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a718:   0x22   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a720:   0xab   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a728:   0x09   0x00   0x00   0x00   0x00   0x00   0x00   0x00

This is a contiguous piece of storage space that holds the actual data.

Next, in the same way, take another look at the memory layout of iis.

1
2
3
4
5
6
7
(dlv) p &iis
(*[]interface {})(0xc00003a758)
(dlv) x -fmt hex -len 32 0xc00003a758
0xc00003a758:   0x00   0x00   0x09   0x00   0xc0   0x00   0x00   0x00
0xc00003a760:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a768:   0x04   0x00   0x00   0x00   0x00   0x00   0x00   0x00
0xc00003a770:   0xd0   0xa7   0x03   0x00   0xc0   0x00   0x00   0x00

The layout of the slice is the same as is, the main difference is the data it points to.

1
2
3
4
5
6
7
8
9
(dlv) x -fmt hex -len 64 0xc000090000
0xc000090000:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
0xc000090008:   0xa8   0xee   0x0a   0x01   0x00   0x00   0x00   0x00
0xc000090010:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
0xc000090018:   0x10   0xed   0x0a   0x01   0x00   0x00   0x00   0x00
0xc000090020:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
0xc000090028:   0x58   0xf1   0x0a   0x01   0x00   0x00   0x00   0x00
0xc000090030:   0x00   0xe4   0x05   0x01   0x00   0x00   0x00   0x00
0xc000090038:   0x48   0xec   0x0a   0x01   0x00   0x00   0x00   0x00

Looking closely at the data above, the contents of the even-numbered rows are all the same, this is the itab address of interface{}. The contents of the odd-numbered lines are different and point to the actual data.

Print the contents of the address.

1
2
3
4
5
6
7
8
(dlv) x -fmt hex -len 8 0x010aeea8
0x10aeea8:   0x55   0x00   0x00   0x00   0x00   0x00   0x00   0x00
(dlv) x -fmt hex -len 8 0x010aed10
0x10aed10:   0x22   0x00   0x00   0x00   0x00   0x00   0x00   0x00
(dlv) x -fmt hex -len 8 0x010af158
0x10af158:   0xab   0x00   0x00   0x00   0x00   0x00   0x00   0x00
(dlv) x -fmt hex -len 8 0x010aec48
0x10aec48:   0x09   0x00   0x00   0x00   0x00   0x00   0x00   0x00

Obviously, by printing the state of the program in operation, it is consistent with our theoretical analysis.

Generic method

With the above analysis, we know the reason why we can’t convert, so is there a generic method? Because I really don’t want to write more lines of code each time.

Yes, there is, using reflect, but the disadvantage is obvious, the efficiency will be worse, it is not recommended to use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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
}

Is there any other way? The answer is the generic support of Go 1.18, which is not described here, so you can continue to study it if you are interested.