ChatGPT解决这个技术问题 Extra ChatGPT

类型转换接口切片

我很好奇为什么 Go 在将 T 隐式转换为 interface{} 时不会将 []T 隐式转换为 []interface{}。我错过了这种转换是否有一些重要的东西?

例子:

func foo([]interface{}) { /* do something */ }

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

go build 投诉

不能在函数参数中使用 (type []string) 作为类型 []interface {}

如果我尝试明确地这样做,同样的事情:b := []interface{}(a) 抱怨

无法将 (type []string) 转换为 type []interface {}

所以每次我需要进行这种转换(这似乎经常出现)时,我一直在做这样的事情:

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

有没有更好的方法来做到这一点,或者标准库函数来帮助这些转换?每次我想调用一个可以获取整数或字符串列表的函数时,多写 4 行代码似乎有点愚蠢。

因此,您将“隐式转换 []T 到 []interface{}”定义为“分配一个新切片并复制所有元素”。这与“将 T 隐式转换为 interface{}”的做法不一致,后者只是将值视为更通用的静态类型的视图;没有任何东西被复制;如果你将它类型断言回类型 T,你仍然会得到同样的东西。
并没有详细考虑如何实现它,只是在更高级别上,能够轻松地将整个列表转换为另一种类型作为没有泛型类型的解决方法会很方便。
多年后,希望出现了:Go Generics 即将推出,并且已经有一个游乐场,您可以在其中看到并尝试它们go2goplay.golang.org(背景:go.dev/blog/generics-next-step

b
blackgreen

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

它可能会更慢,但它通常适用于任何类型的切片
整个答案也适用于map
这也适用于 channels
感谢您的明确解释,如果可能的话,您可以添加参考。 Converting a []string to an interface{} is also done in O(1) time
为此答案添加了基准:stackoverflow.com/a/69309292。你是对的 - 使用循环快 33%。
R
RJFalconer

您缺少的是具有 T 值的 Tinterface{} 在内存中具有不同的表示形式,因此无法进行简单的转换。

T 类型的变量只是它在内存中的值。没有关联的类型信息(在 Go 中,每个变量都有一个在编译时而不是在运行时已知的单一类型)。它在内存中表示如下:

价值

保存类型为 T 的变量的 interface{} 在内存中表示如下

指向类型 T 的指针

价值

所以回到你原来的问题:为什么 go 不会将 []T 隐式转换为 []interface{}

[]T 转换为 []interface{} 将涉及创建一个新的 interface {} 值切片,这是一个不平凡的操作,因为内存中的布局完全不同。


这是内容丰富且写得很好(+1),但你没有解决他问题的另一部分:“有没有更好的方法来做到这一点......”(-1)。
Y
Yandry Pozo

以下是官方解释: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
}

很好的信息,但这不是官方解释。这是一个每个人都可以编辑的wiki。
我应该如何将 []interface 转换为我期望的类型,例如 []map[string]interface{}
d
dskinner

请改用 interface{}。要作为切片回滚,请尝试

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

这就引出了下一个问题:OP 应该如何迭代 bar 以便将其解释为“任何类型的切片”?请注意,他的三行创建一个 []interface{},而不是 []string 或其他具体类型的切片。
-1:仅当 bar 为 [] 字符串时才有效,在这种情况下,您不妨写:func foo(bar []string) { /* ... */ }
使用类型开关,或在接受的答案中使用反射。
OP 将不得不在运行时以任何方式找出他的类型。使用非切片 interface{} 将使编译器在传递 foo(a) 中的参数时不会抱怨。他可以在 foo 中使用反射或类型切换 (golang.org/doc/effective_go.html#type_switch)。
2
2 revs user12258482

在 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 的需要。下面是需要转换的示例:应用程序希望 query a database 使用 []string 的元素作为声明为 args ...any 的可变参数查询参数。此答案中的功能允许应用程序以方便的单行方式查询数据库:

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

R
RaditzLawliet

如果您需要更多缩短代码,您可以为助手创建新类型

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

N
Nik S

我很好奇通过反射转换接口数组与在循环中执行它相比要慢多少,如 Stephen's answer 中所述。这是两种方法的基准比较:

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

因此,使用循环比通过反射快约 20%。

这是我的测试代码,以防您想验证我是否正确地做事:

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


该语言提供了所有工具来编写可重现的基准测试、分析代码,以有效地了解幕后发生的事情。另请参阅 prettybenchbenchcmp
@mh-cbon 好的,使用 -bench 更新了基准。
c
colm.anseo

interface{} 转换为任何类型。

句法:

result := interface.(datatype)

例子:

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

你确定吗?我不认为这是有效的。你会看到它:invalid type assertion: potato.([]string) (non-interface type []interface {} on left)