Go接口:静态与动态绑定

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.

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.

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.