本文转载自微信公众号《Golang技术分享》,作者:机器贝尔斩波。转载本文请联系Golang技术分享公众号。在Go语言的日常编码工作中,有一个很常见但很诡异的编译错误,一度让我很困惑。相信很多Gopher都遇到过这个问题,一起来看看吧。背景回顾我们定义了一个带有WriteGoCode()方法的Gopher接口,同时定义了一个person结构体,其中有一个WriteGoCode()方法。typeGopherinterface{WriteGoCode()}typepersonstruct{namestring}func(pperson)WriteGoCode(){fmt.Printf("Iam%s,iamwritinggocode!\n",p.name)}在Go语言中,只要一个对象拥有所有的方法,对象实现接口。p是person结构体的实例化对象,Coding()函数的入参是Gopher接口,person对象实现了Gopher接口,所以p的入参执行成功。funcCoding(gGopher){g.WriteGoCode()}funcmain(){p:=person{name:"小菜刀"}Coding(p)}//输出:我是小菜刀,我是writinggocode!此时,我们将Coding()函数的入参改为[]Gopher类型,入参为[]person。funcCoding(gGopher){g.WriteGoCode()}funcmain(){p:=person{name:"小菜刀"}Coding(p)}//输出:我是小菜刀,我是writinggocode!但是此时编译不能通过!./main.go:29:8:cannotusep(type[]person)astype[]GopherinargumenttoCoding,很明显person类型实现了Gopher接口,函数输入时参数是Gopher类型,可以顺利执行,但是参数变成了[]Gopher编译失败,为什么?通用语法规则的问题在stackoverflow上已经被热议。详情见文末参考链接1。在Go中,有一个通用规则,即语法不应隐藏复杂/昂贵的操作。将字符串转换为interface{}是O(1),将[]string转换为interface{}也是O(1)操作,因为它仍然是单值转换。如果您要将[]string转换为[]interface{},这是一个O(N)操作。因为切片的每个元素都要转为interface{},这违反了Go的语法原则。你同意这个答案吗?当然,这条规则有一个例外:转换字符串。将字符串转换为[]byte或[]rune时,即使需要O(n)操作,Go也会允许。InterfaceSlice问题IanLanceTaylor(Go核心开发者)也在Go官方仓库中回答了这个问题,详见文末参考链接2。他给出了这样做的两个主要原因。原因一:[]interface{}类型的变量不是接口!它只是一个切片,其元素恰好是interface{}类型。原因2:[]interface{}变量有特定大小的内存布局,编译时可以知道。这与[]MyType不同。每个interface{}(运行时用runtime.eface表示)占用两个词(一个词表示包含内容的类型_type,另一个词表示包含的数据data或指向它的指针)。因此,typeis[]interface{}是一个长度为N的变量,由一个N*2字的数据块支持。这与[]MyType类型的长度为N的变量的块大小不同,因为后一个块的长度为N*sizeof(MyType)个字。结果是编译器无法快速将[]MyType类型的内容赋值给[]interface{}类型的内容。同样,[]Gopher变量也是一个特定大小的内存布局(运行时用runtime.iface表示)。将[]MyType类型的内容分配给[]Gopher类型也不够快。因此,IanLanceTaylor的回答以Go语法的一般规则结束了循环:Go语法不应隐藏复杂/昂贵的操作,编译器会拒绝它们。代码解决方案再次附上文章开头的例子。如果我们需要[]类型的p能够顺利进入Coding()函数,应该怎么做。funcCoding(gs[]Gopher){for_,g:=rangegs{g.WriteGoCode()}}funcmain(){p:=[]person{{name:"1号小菜刀"},{name:"小菜刀2号"},}Coding(p)}的代码方案如下,核心是需要一个[]Gopher类型的转换变量。funcmain(){p:=[]person{{name:"一号菜刀"},{name:"二号小菜刀"},}varinterfaceSlice[]Gopher=make([]Gopher,len(p))fori,g:=rangep{interfaceSlice[i]=g}Coding(interfaceSlice)}//输出:我是1号小菜刀,我写gocode!我是小菜刀2号,我在写gocode!总结由于[]MyType到[]interface{}是一个昂贵的操作,Go编译器将不允许这个通过编译,从而将这个开销的责任转嫁给开发者。Go是一门编译速度很快的语言,这要归功于它在语法设计上贯彻了“越简单越好”的理念,这可不是说说而已。参考链接[1.类型转换接口切片]https://stackoverflow.com/questions/12753805/type-converting-slices-of-interfaces/12754757#12754757[2.InterfaceSlice]https://github.com/golang/go/wiki/InterfaceSlice
