Go接口:静态绑定 vs 动态绑定

huangapple go评论74阅读模式
英文:

Go interfaces: static vs dynamic binding

问题

Go同时使用动态和静态绑定。根据我的理解,如果需要使用类型断言,则是动态绑定。我想要验证我的假设。

假设:

foo := Foo{}

// 静态绑定:Foo -> XYer
var xy XYer = foo

// 静态绑定:XYer -> Xer
var x Xer = xy

// 静态绑定:Xer -> interface{}
var empty interface{} = x

// 动态绑定:interface{} -> XYer
xy2 := empty.(XYer)

// 动态绑定:XYer -> Foo
foo2 := xy2.(Foo)

因此,当从type A转换为interface B时,如果A满足B,则不需要断言,itable可以在编译时生成。那么在不需要断言的情况下使用断言会发生什么呢?

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

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

英文:

Go uses both dynamic and static binding. From my understanding, if you need to use a type assertion then it's dynamic. I want to validate my assumptions.

type Xer interface { 
  X()
}

type XYer interface {
  Xer
  Y()
}

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

Assumptions:

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)

So when converting from type A -> interface B, if A satisfies B then you don't need an assertion and the itable can be generated at compile time. What about a case where you use an assertion where it's not needed:

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

what happens in this case? If someone could clarify this for me that would great.

答案1

得分: 6

我不知道什么是接口的静态绑定或动态绑定。语言规范从未提到过这些术语。让我假设你指的是编译时类型检查和运行时类型检查。在这个假设下,你的所有例子在我看来都是正确的。它归结为一个简单的模式(相关语言规范部分的简化版本):

  • 所有赋值在编译时进行类型检查。
  • 所有类型断言(.(T))在运行时进行类型检查。

这也回答了“在这种情况下会发生什么?”的问题。

也就是说,编译器可以自由地优化那些在编译时可以证明类型已知且赋值兼容的情况。但这只是一个实现细节,不能依赖它,因为其他符合规范的实现可能不是这样的情况。

实际上,gc编译器(如果我没记错的话)有这样的优化,甚至相反的情况也是如此。它可以在编译时证明类型断言将失败,从而说“不可能的类型断言”。

英文:

I don't know what is static binding of interfaces or dynamic binding of interfaces. The language specification never mentions such terms. Let me assume you mean type checking at compile time and type checking at run time. With this assumptions, all of your examples are, AFAICS, correct. It boils down to a simple schema (a condensed version of the relevant language specification parts):

  • All assignments are type checked at compile time.
  • All type assertions (.(T)) are type checked at run time.

Which also answers the "what happens in this case?".

That said, the compiler is free to optimize cases where it can prove at compile time that the type is known and assignment compatible. But that's only an implementation detail which one cannot rely on - as it may not be the case of other, specification conforming implementation(s).

Actually the gc compiler has (IINM) such optimizations and even in the opposite sense. It can say 'impossible type assertion' where it can prove at compile time that the type assertion will fail.

答案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代表Interface to Eface(空接口),因此不需要检查方法。assertI2E强制执行的唯一限制是断言的值必须是一个接口。

然而,如果你执行的是x.(Xer),则会调用runtime.assertI2I,然后会检查方法是否实现了接口。

英文:

To extend on jnml's answer, 6g generates a type assertion nevertheless.

empty := x.(interface{})

is expanded to:

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)

To clarify what is happening here, in line 34 the InterfaceType of interface{} is loaded to the
first value of the stack. Line 35-36 and 37-38 put the tab and data values of x
onto the stack. The stack is then ready for runtime.assertI2E to be called,
which simply assigns the underlying type and data to the return value. The compiler knows that
you're assigning to an empty interface, hence the call to assertI2E: I2E stands for Interface to Eface (Empty Interface), so no check for methods is necessary. The only restriction assertI2E enforces is that
the asserted value must be an interface.

If however, you're doing x.(Xer), runtime.assertI2I would've been called, which then checks if
the methods implement the interface.

huangapple
  • 本文由 发表于 2013年4月11日 23:15:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/15952519.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定