切片切片的拆包

Unpacking slice of slices

我很好奇解包切片并将它们作为参数发送给可变参数函数。

假设我们有一个带有可变参数的函数:

func unpack(args ...interface{})

如果我们不想传入它工作的接口片段,我们是否解压它并不重要:

slice := []interface{}{1,2,3}
unpack(slice) // works
unpack(slice...) // works

如果我们有一片切片,它会变得棘手。这里编译器不允许我们传入解压后的版本:

sliceOfSlices := [][]interface{}{
    []interface{}{1,2},
    []interface{}{101,102},
}
unpack(sliceOfSlices) // works
unpack(sliceOfSlices...) // compiler error

错误说:

cannot use sliceOfSlices (type [][]interface {}) as type []interface {} in argument to unpack

我不知道为什么会这样,因为我们可以清楚地将 []interface{} 类型传递给函数。如何以 sliceOfSlices 的解压内容作为参数调用 unpack 方法?

游乐场示例:https://play.golang.org/p/O3AYba8h4i

这在 Spec: Passing arguments to ... parameters 中有介绍:

If f is variadic with a final parameter p of type ...T, then within f the type of p is equivalent to type []T.

...

If the final argument is assignable to a slice type []T, it may be passed unchanged as the value for a ...T parameter if the argument is followed by .... In this case no new slice is created.

所以简而言之:这是一个编译时错误,因为sliceOfSlices(属于[][]interface{}类型)无法分配给args(类型为 []interface{})(proof on Playground)。

长:

在您执行 unpack(slice) 的第一个示例中,由于 unpack() 期望值为 interface{},因此 slice(属于 []interface{} 类型)将被包装在 new interface{} 值中,并将作为 单个 参数传递。

当您执行 unpack(slice...) 时,这会将 slice 的所有值作为单独的值传递给 unpack();这是可能的,因为 slice 的类型是 []interface{},它匹配可变参数的类型 (args ...interface{}).

在您的第二个示例中,当您执行 unpack(sliceOfSlices) 时,sliceOfSlices 将再次包装在 new interface{} 值中并传递为单个参数。

但是当你尝试 unpack(sliceOfSlices...) 时,它会想要将 sliceOfSlices 的每个元素传递给 unpack(),但是 sliceOfSlices 的类型(即 [][]interface{}) 与可变参数的类型不匹配,因此出现编译时错误。

sliceOfSlices 传递给 unpack() "exploded" 的唯一方法是创建一个类型必须为 []interface{} 的新切片,复制元素,然后您可以使用....

示例:

var sliceOfSlices2 []interface{}
for _, v := range sliceOfSlices {
    sliceOfSlices2 = append(sliceOfSlices2, v)
}

unpack(sliceOfSlices2...)

Go Playground 上试试。

让我们使用下面的unpack()函数来验证参数的数量:

func unpack(args ...interface{}) {
    fmt.Println(len(args))
}

运行 您的示例(以及我创建的新切片),输出为:

1
3
1
2

这证明没有 ... 只传递了一个参数(包装在 interface{} 中),而使用 ... 所有元素将单独传递。

Go Playground 上尝试这个测试。