-
发生时间:2022-09-09
-
问题描述:在开发产线平台时,想将一个结构体的 slice 赋值给 interface slice,结果编译报错。(考虑到简洁性,下面使用示意代码)
package main import "fmt" // Named represents anything that has a name. type Named interface { GetName() string } // Person has a name type Person struct { Name string Addr string Age int } // GetName gets the name of a person. func (p Person) GetName() string { return p.Name } // showNames shows the names of a group. func showNames(names []Named) { for _, n := range names { fmt.Println(n.GetName()) } } func main() { persons := []Person{Person{"guojing", "damo", 20}, Person{"huangrong", "taohuadao", 16}} showNames(persons) }$ go run named.go ./named.go:31:11: cannot use persons (type []Person) as type []Named in argument to showNames -
问题类别:软件开发
-
原因分析:
- 按照我的直觉,既然我们可以将
Person类型的变量赋值给了Named变量,那么[]Person类型的变量也应该可以赋值给[]Named变量。 - 根据编译器的报错信息,
[]Person赋值给[]Named是不被允许的,这说明我的直觉是错误的。 - 搜索网上资料,在 stack overflow QA1 和 QA2 找到解释:
[]Person与[]Named的内存布局不一样,这是因为Person与Named的内存布局不一样: 前者的长度取决于其包含的字段,后者的长度固定为 2 words。- 如果要将
[]Person转换为[]Named,需要遍历 slice 的每个元素进行转换,其时间复杂度为 O(n)。 - 由于其时间复杂度为 O(n),并且会导致创建一个新的 slice, Go 语言不允许隐式进行这种转换。
- 此外,Google Group 从设计哲学、内部实现等方面讨论了这个问题,博客 Go Data Structures: Interfaces 则介绍了
interface{}的实现原理。 阅读这些资料可以加深对这个问题的理解。
- 按照我的直觉,既然我们可以将
-
解决方案:将
showNames的输入参数类型改为interface{},并使用 reflect 来获取其原来的类型。package main import ( "fmt" "reflect" ) // Named represents anything that has a name. type Named interface { GetName() string } // Person has a name type Person struct { Name string Addr string Age int } // GetName gets the name of a person. func (p Person) GetName() string { return p.Name } // showNames shows the names of a group. func showNames(names interface{}) { if reflect.TypeOf(names).Kind() != reflect.Slice { return } v := reflect.ValueOf(names) for i := 0; i < v.Len(); i++ { if val, ok := v.Index(i).Interface().(Named); ok { fmt.Println(val.GetName()) } } } func main() { persons := []Person{Person{"guojing", "damo", 20}, Person{"huangrong", "taohuadao", 16}} showNames(persons) } -
实施结果:程序可以编译通过,并正常运行。
$ go run named.go guojing huangrong -
经验总结:
- Go 语言中,不能将结构体 slice 赋值给 interface slice。
- 如果要实现相关功能,Go 1.18 以前,可以使用 reflect 机制;Go 1.18 以后,可以使用泛型。