interface(struc)和interface(struc).function到底是什么

Trying to do go koan, i got stuck in understanding the interface(struct) syntax, what exactly does it do ? I came up with following fun program, which has further confused me on how is interface casting working :

package main

import "fmt"

type foo interface{  fn() }

type t struct { }
type q struct { }

func (_i t ) fn() { fmt.Print("t","
") }
func (_i q ) fn() { fmt.Print("q","
")}

func main() {
    _j :=  t{}
    _q :=  q{}

    // This is alright ..
    fmt.Print( _j.fn,"
")           //0x4015e0     
    fmt.Print( _q.fn,"
")       //0x401610     
    _j.fn()              //t           
    _q.fn()              //q           
    // both pointers same .. why ?
    fmt.Print( foo(_j).fn,"
")  //0x401640     
    fmt.Print( foo(_q).fn,"
")  //0x401640     
    // but correct fns called .. how ?
    foo(_j).fn()             //t           
    foo(_q).fn()             //q           

    // same thing again ...
    _fj := foo(_j).fn         
    _fq := foo(_q).fn         
    // both pointers same .. as above
    fmt.Print( _fj,"
")         //0x401640    
    fmt.Print( _fq,"
")         //0x401640    
    // correct fns called .. HOW !
    _fj()                //t                          
    _fq()                //q           
}

The pointer are what i'm getting my machin, YMMV. My question is .. what exactly does interface(struct) returns ? and how does interface(struct).func , finds the original struct ... is there some thunk/stub magic going on here?

From here: http://research.swtch.com/interfaces

enter image description here

what exactly does interface(struct) return?

It creates a new interface value (like the one you see on top in the graphic), wrapping a concrete struct value.

how does interface(struct).func find the original struct?

See the data field in the graphic. Most of the time this will be a pointer to an existing value. Sometimes it will contain the value itself if it fits, though.

In the itable you'll see a function table (where fun[0] is).

I assume that on your machine 0x401640 is the address of the respective pointers to fn, which is in that table for foo. Although this is best verified by someone working on the GC compiler suite.

Note that the behaviour you discovered is not strictly defined to be so. Compiler builders can take other approaches to implementing Go interfaces if they like to, as long as the language semantics are preserved.


Edit to answer questions in the comments:

package main

import "fmt"

type foo interface {
    fn()
}

type t struct{}
type q struct{}

func (_i t) fn() { fmt.Print("t", "
") }
func (_i q) fn() { fmt.Print("q", "
") }

func main() {
    _j := t{}
    _j1 := t{}

    fmt.Println(foo(_j) == foo(_j))  // true
    fmt.Println(foo(_j) == foo(_j1)) // true
}

On the diagram you see 3 blocks:

  • The one on the left side labeled Binary is a concrete type instance, like your struct instances _j and _j1.

  • The one on the top center is an interface value, this one wraps (read: points to) a concrete value.

  • The block on the right lower side is the interface definition for Binary underlyings. This is where the jump table / call forwarding table is (itable).

_j and _j1 are two instances of the concrete type t. So there are two of the lower-left blocks somewhere in memory.

Now you decide to wrap both _j and _j1 in interfaces values of type foo; now you have 2 of the top-center blocks somewhere in memory, pointing back at _j and _j1.

In order for the interface value to remember what its underlying type is and where the methods of those types are it keeps a single instance of the lower-right block in memory, to which both interface values for _j and _j1 respectively point to.

In that block you have a jump table to forward method calls made on the interface values to the concrete underlying type's implementation. That's why both are the same.

It's worth mentioning that unlike Java and C++ (not sure about Python), all Go methods are static and the dot-call notation is only syntactic sugar. So _j and _j1 don't have different fn methods, it's the same exact method called with another implicit first parameter which is the receiver on which the method is called.