Go语言接口:静态绑定与动态绑定

4

Go使用动态和静态绑定。据我所知,如果您需要使用类型断言,则是动态的。我希望验证我的假设。


type Xer interface { 
  X()
}

type XYer interface {
  Xer
  Y()
}

type Foo struct{}
func (Foo) X() { println("Foo#X()") }
func (Foo) Y() { println("Foo#Y()") }

假设:

foo := Foo{}

// static: Foo -> XYer
var xy XYer = foo

// static: XYer -> Xer
var x Xer = xy

// static: Xer -> interface{}
var empty interface{} = x

// dynamic: interface{} -> XYer
xy2 := empty.(XYer)

// dynamic: XYer -> Foo
foo2 := xy2.(Foo)

当从类型A转换为接口B时,如果A满足B,则不需要断言并且itable可以在编译时生成。那么在不需要断言的情况下使用断言的情况怎么处理:

var x Xer = Foo{}
empty := x.(interface{})

在这种情况下会发生什么?如果有人能为我澄清一下,那就太好了。

为什么不直接查看它输出的汇编代码呢? - cthom06
1
@cthom06 因为我太蠢了。 - Ilia Choly
1
参见 - kostix
2个回答

6
扩展jnml的回答,6g仍会生成类型断言。
empty := x.(interface{})

被扩展为:

0034 (dumb.go:19) MOVQ    $type.interface {}+0(SB),(SP)
0035 (dumb.go:19) LEAQ    8(SP),BX
0036 (dumb.go:19) MOVQ    x+-32(SP),BP
0037 (dumb.go:19) MOVQ    BP,(BX)
0038 (dumb.go:19) MOVQ    x+-24(SP),BP
0039 (dumb.go:19) MOVQ    BP,8(BX)
0040 (dumb.go:19) CALL    ,runtime.assertI2E+0(SB)
0041 (dumb.go:19) MOVQ    24(SP),BX
0042 (dumb.go:19) MOVQ    BX,empty+-16(SP)
0043 (dumb.go:19) MOVQ    32(SP),BX
0044 (dumb.go:19) MOVQ    BX,empty+-8(SP)

为了解释这里正在发生的事情,在第34行,加载了interface{}InterfaceType到堆栈的第一个值。第35-36行和37-38行将xtab和data值放到堆栈上。然后堆栈就准备好调用runtime.assertI2E了,它简单地将底层类型和数据赋给返回值。编译器知道你正在给一个空接口赋值,因此调用assertI2EI2E代表接口到Eface (空接口),所以不需要检查方法。assertI2E强制执行的唯一限制是断言的值必须是接口。
然而,如果你在做x.(Xer),则会调用runtime.assertI2I,然后检查方法是否实现了接口。

6
我不知道什么是接口的静态绑定或动态绑定。语言规范从未提到过这些术语。让我假设你指的是编译时类型检查和运行时类型检查。在这个假设下,所有的例子都是正确的。它归结为一个简单的模式(相关语言规范部分的压缩版本):
  • 所有的赋值都在编译时进行类型检查。
  • 所有的类型断言 (.(T)) 都在运行时进行类型检查。
这也回答了“在这种情况下会发生什么?”的问题。
话虽如此,编译器可以优化那些它能够证明在编译时已知类型且可赋值兼容的情况。但这只是一个实现细节,不能依赖它 - 因为其他符合规范的实现可能不是这种情况。
实际上,gc 编译器有(如果我没记错的话)这样的优化,甚至相反的情况也是如此。它可以说“不可能的类型断言”,其中它可以在编译时证明类型断言将失败。

我是根据我在这里阅读的内容所做出的推断(http://research.swtch.com/interfaces)。它说方法表是在运行时生成的。但如果一个赋值操作(给接口变量)在编译时进行类型检查,那么我认为它也可以在编译时为这些接口生成表。 - Ilia Choly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接