类型转换接口的切片

huangapple go评论84阅读模式
英文:

Type converting slices of interfaces

问题

我很好奇为什么Go在将[]T隐式转换为[]interface{}时,却不会隐式转换Tinterface{}。这个转换有什么非常复杂的地方我没有注意到吗?

示例:

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(&quot;InterfaceSlice() given a non-slice type&quot;)
    }

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

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

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

    return ret
}

答案2

得分: 76

你所忽略的是,Tinterface{}(它保存了一个 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{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;}
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 (
    	&quot;math/rand&quot;
    	&quot;reflect&quot;
    	&quot;testing&quot;
    	&quot;time&quot;
    )
    
    func InterfaceSlice(slice interface{}) []interface{} {
    	s := reflect.ValueOf(slice)
    	if s.Kind() != reflect.Slice {
    		panic(&quot;InterfaceSlice() given a non-slice type&quot;)
    	}
    
    	// Keep the distinction between nil and empty slice input
    	if s.IsNil() {
    		return nil
    	}
    
    	ret := make([]interface{}, s.Len())
    
    	for i := 0; i &lt; s.Len(); i++ {
    		ret[i] = s.Index(i).Interface()
    	}
    
    	return ret
    }
    
    type TestStruct struct {
    	name string
    	age  int
    }
    
    var letters = []rune(&quot;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;)
    
    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 &lt; lenMap; i++ {
    		var testStructs = make([]TestStruct, 0)
    		for k := 0; k &lt; 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 &lt; 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 &lt; 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{&quot;hello&quot;, &quot;world&quot;}
    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{&quot;Jhon&quot;, &quot;Arya&quot;}
result := employee.([]string)   //result type is []string.

huangapple
  • 本文由 发表于 2012年10月6日 04:48:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/12753805.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定