gopls与go test对Go接口实现判定不一致的问题排查
Go接口嵌入与类型断言问题分析
问题代码
package ifaces import ( "fmt" "testing" ) type IFace1 interface { Do1() } type IFace2 interface { Do2() } type Inner struct { } func (i *Inner) Do2() { fmt.Printf("Do2\n") } type Outer struct { Inner } func (o *Outer) Do1() { fmt.Printf("Do2\n") } func TestInterface(t *testing.T) { o := Outer{} var i interface{} i = o a := i.(IFace2) a.Do2() }
问题现象
Inner通过指针接收者实现了IFace2接口,Outer嵌入Inner结构体并实现了IFace1接口。- 使用vim-go的
GoImplements命令调用gopls检查Outer时,返回结果显示Outer关联了IFace1和IFace2两个接口:
test/ifaces/interfaces_test.go|8 col 6| type IFace1 interface { test/ifaces/interfaces_test.go|12 col 6| type IFace2 interface {
- 但运行测试时触发panic,错误信息如下:
$ go test test/ifaces/interfaces_test.go --- FAIL: TestInterface (0.00s) panic: interface conversion: ifaces.Outer is not ifaces.IFace2: missing method Do2 [recovered] panic: interface conversion: ifaces.Outer is not ifaces.IFace2: missing method Do2 goroutine 18 [running]: testing.tRunner.func1.2({0x5095c0, 0xc0000a23c0}) /home/x/sdk/go1.19.3/src/testing/testing.go:1396 +0x24e testing.tRunner.func1() /home/x/sdk/go1.19.3/src/testing/testing.go:1399 +0x39f panic({0x5095c0, 0xc0000a23c0}) /home/x/sdk/go1.19.3/src/runtime/panic.go:884 +0x212 command-line-arguments.TestInterface(0x0?) /home/x/D/git/personal/go/test/ifaces/interfaces_test.go:36 +0x27 testing.tRunner(0xc0000829c0, 0x52f520) /home/x/sdk/go1.19.3/src/testing/testing.go:1446 +0x10b created by testing.(*T).Run /home/x/sdk/go1.19.3/src/testing/testing.go:1493 +0x35f FAIL command-line-arguments 0.004s FAIL
- 该问题在Go 1.19和1.13.15版本中均会出现,并非新版本bug。
问题原因
- 方法提升规则限制:
Do2方法的接收者是*Inner指针类型,而Outer嵌入的是值类型的Inner。根据Go的方法提升规则:- 嵌入值类型时,只有接收者为值类型的方法会被提升到外层结构体;
- 嵌入指针类型时,值和指针接收者的方法都会被提升。
因此值类型的Outer并没有Do2方法,无法实现IFace2接口。
- gopls判定偏差:gopls检测到的是指针类型
*Outer可以通过嵌入的Inner间接调用Do2(*Outer可获取*Inner),所以判定*Outer实现了IFace2,但测试中使用的是值类型Outer,两者并非同一类型,导致类型断言失败。 - interface{}变量不影响结果:将
Outer存入interface{}只是类型装箱操作,并未改变其本身类型,不是错误根源。
解决方案
方案1:将Outer的嵌入类型改为指针
修改Outer结构体定义,嵌入*Inner:
type Outer struct { *Inner }
此时值类型Outer可提升*Inner的Do2方法,满足IFace2接口要求。
方案2:为值类型Outer添加Do2方法
手动实现Do2方法,内部调用嵌入的Inner的Do2:
func (o Outer) Do2() { o.Inner.Do2() // 自动取地址匹配*Inner接收者 }
方案3:测试中使用指针类型Outer
修改测试代码,使用*Outer实例:
func TestInterface(t *testing.T) { o := &Outer{} var i interface{} i = o a := i.(IFace2) a.Do2() }
*Outer可通过嵌入的Inner获取*Inner,从而拥有Do2方法,实现IFace2接口。
内容的提问来源于stack exchange,提问作者caxcaxcoatl
相关产品推荐
相关产品推荐

