英文:
what exactly is interface(struc) and interface(struc).function
问题
尝试解决一个问题时,我在理解接口(struct)语法时遇到了困难,它到底是做什么的?
我想出了下面这个有趣的程序,但它进一步让我对接口转换的工作原理感到困惑:
package main
import "fmt"
type foo interface{ fn() }
type t struct{}
type q struct{}
func (_i t) fn() { fmt.Print("t", "\n") }
func (_i q) fn() { fmt.Print("q", "\n") }
func main() {
_j := t{}
_q := q{}
// This is alright ..
fmt.Print(_j.fn, "\n") //0x4015e0
fmt.Print(_q.fn, "\n") //0x401610
_j.fn() //t
_q.fn() //q
// both pointers same .. why ?
fmt.Print(foo(_j).fn, "\n") //0x401640
fmt.Print(foo(_q).fn, "\n") //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, "\n") //0x401640
fmt.Print(_fq, "\n") //0x401640
// correct fns called .. HOW !
_fj() //t
_fq() //q
}
指针是我在我的机器上得到的,可能会有所不同。我的问题是,接口(struct)到底返回什么?
接口(struct)。func 如何找到原始的结构体...
这里是否有一些 thunk/stub 魔法?
英文:
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","\n") }
func (_i q ) fn() { fmt.Print("q","\n")}
func main() {
_j := t{}
_q := q{}
// This is alright ..
fmt.Print( _j.fn,"\n") //0x4015e0
fmt.Print( _q.fn,"\n") //0x401610
_j.fn() //t
_q.fn() //q
// both pointers same .. why ?
fmt.Print( foo(_j).fn,"\n") //0x401640
fmt.Print( foo(_q).fn,"\n") //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,"\n") //0x401640
fmt.Print( _fq,"\n") //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?
答案1
得分: 3
从这里开始:http://research.swtch.com/interfaces
> interface(struct)
究竟返回什么?
它创建一个新的接口值(就像你在图中看到的顶部那个),包装了一个具体的结构体值。
> interface(struct).func
如何找到原始的结构体?
请看图中的 data 字段。大多数情况下,这将是一个指向现有值的指针。有时,如果适合的话,它将包含值本身。
在 itable 中,你会看到一个函数表(其中 fun[0] 位于其中)。
我假设在你的机器上,0x401640
是指向 fn
的相应指针的地址,该指针在 foo
的表中。尽管最好由 GC 编译器套件的开发人员进行验证。
请注意,你发现的行为并没有严格定义为这样。如果编译器构建者愿意,他们可以采用其他方法来实现 Go 接口,只要保留语言语义即可。
<hr>
编辑以回答评论中的问题:
package main
import "fmt"
type foo interface {
fn()
}
type t struct{}
type q struct{}
func (_i t) fn() { fmt.Print("t", "\n") }
func (_i q) fn() { fmt.Print("q", "\n") }
func main() {
_j := t{}
_j1 := t{}
fmt.Println(foo(_j) == foo(_j)) // true
fmt.Println(foo(_j) == foo(_j1)) // true
}
在图中,你可以看到 3 个块:
-
左侧标记为 Binary 的块是一个具体类型的实例,就像你的结构体实例
_j
和_j1
。 -
顶部中心的块是一个接口值,它包装(指向)一个具体值。
-
右下方的块是 Binary 底层的接口定义。这是跳转表/调用转发表所在的地方(itable)。
_j
和 _j1
是具体类型 t
的两个实例。因此,在内存中有两个左下方的块。
现在,你决定将 _j
和 _j1
都包装在类型为 foo
的接口值中;现在,在内存中有 2 个顶部中心的块,分别指向 _j
和 _j1
。
为了使接口值记住其底层类型以及这些类型的方法在哪里,它在内存中保留了一个__单一__的右下方块的实例,两个 _j
和 _j1
的接口值分别指向它。
在该块中,你有一个跳转表,用于将在接口值上进行的方法调用转发到具体底层类型的实现。这就是为什么它们都是相同的。
值得一提的是,与 Java 和 C++(不确定 Python)不同,所有的 Go 方法都是静态的,点调用符号只是语法糖。所以 _j
和 _j1
没有不同的 fn
方法,它们调用的是完全相同的方法,只是隐含的第一个参数不同,该参数是调用方法的接收器。
英文:
From here: http://research.swtch.com/interfaces
> 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.
<hr>
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", "\n") }
func (_i q) fn() { fmt.Print("q", "\n") }
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论