In Go, if the function argument is interface{}
, it can be called with any argument, and then converted by type assertion.
As an example.
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.
Unfortunately, this code does not compile, and if you try to convert it directly via b := []interface{}(a)
, it still reports an error.
|
|
The correct way to convert needs to be written like this.
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 beN*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.
- the type
[]interface{}
is not aninterface
, it is a slice, except that its elements are of typeinterface
. []interface{}
has a special memory layout, which is different frominterface
.
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.
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 thearray
array.
As an example, create a slice as follows.
|
|
Then its layout is shown in the following figure.
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.
iface
denotes interfaces that contain methods.eface
denotes an empty interface that does not contain methods.
The definitions in the source code are as follows, respectively.
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.
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.
|
|
We use Delve for debugging, which can be installed by clicking here.
|
|
Print the address of is
.
Next, see what the slice contains in memory.
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.
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
.
|
|
The layout of the slice is the same as is
, the main difference is the data it points to.
|
|
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.
|
|
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.
|
|
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.