英文:
Type converting slices of interfaces
问题
我很好奇为什么Go在将[]T
隐式转换为[]interface{}
时,却不会隐式转换T
为interface{}
。这个转换有什么非常复杂的地方我没有注意到吗?
示例:
func foo([]interface{}) { /* 做一些事情 */ }
func main() {
var a []string = []string{"hello", "world"}
foo(a)
}
go build
会报错:
> cannot use a (type []string) as type []interface {} in function argument
如果我尝试显式地进行转换,同样的问题:b := []interface{}(a)
会报错:
>cannot convert a (type []string) to type []interface {}
所以每次我需要进行这种转换时(这似乎经常发生),我都会像这样做:
b = make([]interface{}, len(a), len(a))
for i := range a {
b[i] = a[i]
}
有没有更好的方法来做这个,或者标准库中是否有帮助进行这些转换的函数?每次我想调用一个可以接受整数或字符串列表的函数时,写4行额外的代码似乎有点愚蠢。
英文:
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.
答案1
得分: 326
在Go语言中,有一个通用规则,即语法不应该隐藏复杂/昂贵的操作。
将string
转换为interface{}
的时间复杂度为O(1)。将[]string
转换为interface{}
的时间复杂度也为O(1),因为切片仍然是一个值。然而,将[]string
转换为[]interface{}
的时间复杂度为O(n),因为必须将切片的每个元素转换为interface{}
。
这个规则的一个例外是字符串的转换。当将string
转换为[]byte
或[]rune
,即使转换是“语法”,Go语言也会执行O(n)的工作。
标准库中没有提供可以执行此转换的函数。你最好的选择是使用你在问题中提供的代码行:
b := make([]interface{}, len(a))
for i := range a {
b[i] = a[i]
}
否则,你可以使用reflect
来创建一个函数,但它比三行代码的选项要慢。使用反射的示例:
func InterfaceSlice(slice interface{}) []interface{} {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("InterfaceSlice()给定了一个非切片类型")
}
// 保持nil和空切片输入之间的区别
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
}
英文:
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
}
答案2
得分: 76
你所忽略的是,T
和 interface{}
(它保存了一个 T
类型的值)在内存中有不同的表示方式,所以不能直接进行转换。
T
类型的变量只是其在内存中的值。它没有关联的类型信息(在 Go 中,每个变量在编译时都有一个已知的单一类型,而不是在运行时)。它在内存中的表示方式如下:
- 值
一个保存了 T
类型变量的 interface{}
在内存中的表示方式如下:
- 指向
T
类型的指针 - 值
所以回到你最初的问题:为什么 Go 不会隐式地将 []T
转换为 []interface{}
?
将 []T
转换为 []interface{}
将涉及创建一个新的 interface{}
值的切片,这是一个非平凡的操作,因为内存布局完全不同。
英文:
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.
答案3
得分: 21
这是官方的解释: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
}
英文:
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
}
答案4
得分: 17
在Go 1.18或更高版本中,使用以下函数将任意切片类型转换为[]interface{}
或其别名any
:
func ToSliceOfAny[T any](s []T) []any {
result := make([]any, len(s))
for i, v := range s {
result[i] = v
}
return result
}
Go 1.18泛型特性并不能消除将任意切片转换为[]any
的需求。以下是一个需要进行转换的示例:应用程序希望使用[]string
的元素作为声明为args ...any
的可变参数查询参数来查询数据库。此答案中的函数允许应用程序以方便的一行代码查询数据库:
rows, err := db.Query(qs, ToSliceOfAny(stringArgs)...)
英文:
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)...)
答案5
得分: 10
尝试使用interface{}
代替。要将其转换回切片,请尝试:
func foo(bar interface{}) {
s := bar.([]string)
// ...
}
英文:
Try interface{}
instead. To cast back as slice, try
func foo(bar interface{}) {
s := bar.([]string)
// ...
}
答案6
得分: 5
如果你需要缩短你的代码,你可以为助手创建一个新的类型
type Strings []string
func (ss Strings) ToInterfaceSlice() []interface{} {
iface := make([]interface{}, len(ss))
for i := range ss {
iface[i] = ss[i]
}
return iface
}
然后
a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
英文:
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()
</details>
# 答案7
**得分**: 1
我很好奇通过反射转换接口数组与在循环内部执行转换的速度差异,如[Stephen的回答](https://stackoverflow.com/a/12754757/901019)所述。下面是这两种方法的基准比较:
基准测试 迭代次数 每次耗时 分配的字节数 分配次数
--------- ---- --------- ----------- ------
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
因此,使用循环比通过反射执行转换要快约20%。
以下是我的测试代码,如果您想验证我是否正确执行了操作:
```golang
import (
"math/rand"
"reflect"
"testing"
"time"
)
func InterfaceSlice(slice interface{}) []interface{} {
s := reflect.ValueOf(slice)
if s.Kind() != reflect.Slice {
panic("InterfaceSlice()给定了非切片类型")
}
// 保留nil和空切片输入之间的区别
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
}
}
英文:
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
}
}
</details>
# 答案8
**得分**: 1
[]T(特定类型的切片)不能直接转换为[]interface{}的原因是底层数组的内存布局与存储不同类型元素的接口类型不兼容。
当你有一个切片[]T时,底层数组的内存布局被设计为存储类型为T的元素。数组中的每个元素根据其类型T占用特定的内存空间。
如果允许将[]T转换为[]interface{},那意味着底层数组的内存布局应该能够容纳任何类型的元素,包括interface{}。然而,原始的底层数组被设计为存储类型为T的元素,而不是interface{}或任何其他类型的元素。
问题在于底层数组的内存布局无法同时满足不同类型的要求。例如,如果原始切片是[]string,底层数组的内存布局被优化为存储字符串。
如果你能够将[]string转换为[]interface{},然后尝试在同一个底层数组中存储不同类型(例如int)的元素,那将导致无效的内存布局。为字符串分配的内存空间不适合存储int值,并且以interface{}的形式访问这些值可能会导致意外行为或崩溃。
为了保持类型安全性并保持内存布局的完整性,Go要求你手动创建一个新的[]interface{}切片,并从原始切片中复制元素,执行必要的转换。这确保了新切片的内存布局适合interface{}值,并避免了冲突的内存布局或类型不匹配。
因此,总结起来,不允许从[]T转换为[]interface{},因为底层数组的内存布局特定于元素类型T,无法重新配置以容纳不同类型的元素,否则会导致内存布局冲突或类型不匹配的问题。
<details>
<summary>英文:</summary>
The reason []T (a slice of a specific type) cannot be directly converted to []interface{} is because the memory layout of the underlying array is not compatible with storing elements of different types.
When you have a slice []T, the memory layout of the underlying array is designed to store elements of type T. Each element in the array occupies a specific amount of memory based on its type T.
If you were allowed to convert []T to []interface{}, it would mean that the memory layout of the underlying array should be able to accommodate elements of any type, including interface{}. However, the original underlying array was designed to store elements of type T, not interface{} or any other type.
The issue is that the memory layout of the underlying array cannot simultaneously satisfy the requirements of different types. For example, if the original slice was []string, the memory layout of the underlying array is optimized for storing strings.
If you were able to convert []string to []interface{}, and then try to store elements of a different type (e.g., int) in the same underlying array, it would lead to an invalid memory layout. The memory allocated for strings would not be appropriate for storing int values, and accessing those values as interface{} could result in unexpected behavior or crashes.
To preserve type safety and maintain the integrity of the memory layout, Go requires you to manually create a new slice of []interface{} and copy the elements from the original slice, performing the necessary conversions. This ensures that the memory layout of the new slice is appropriate for interface{} values and avoids conflicting memory layouts or type mismatches.
So, in summary, the conversion from []T to []interface{} is not allowed because the memory layout of the underlying array is specific to the element type T, and it cannot be reconfigured to accommodate elements of different types without causing memory layout conflicts or type mismatches.
</details>
# 答案9
**得分**: 0
虽然你可以使用一个通用函数将切片转换为`interface{}`类型的切片,但如果可能的话,将`foo`函数改为通用函数可能是最合适且执行时间最短的方法。
例如:
```go
func foo[T any](slice []T) { /* 做一些操作 */ }
func main() {
var a []string = []string{"hello", "world"}
foo(a)
}
现在根本不需要进行任何转换。
英文:
Though you can use a generic function to convert a slice to a slice of interface{}
, it may be most appropriate and cheapest in terms of execution time to change foo
to a generic function if possible.
For example:
func foo[T any](slice []T) { /* do something */ }
func main() {
var a []string = []string{"hello", "world"}
foo(a)
}
Now there is no conversion necessary at all.
答案10
得分: -3
将interface{}
转换为任何类型。
语法:
result := interface.(datatype)
示例:
var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string) //result的类型是[]string。
英文:
Convert interface{}
into any type.
Syntax:
result := interface.(datatype)
Example:
var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string) //result type is []string.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论