Go语言Interface源码级解读
(给Go开发大全
加星标)
来源:无敌的CF
https://zhuanlan.zhihu.com/p/86420182
【导读】golang的interface到底是什么?怎么用?它的底层是如何实现的?字节程序带你深入理解interface。
源码
源码基于go v1.13
go的interface有两种,eface和iface
// $GOROOT/src/runtime/runtime2.go
type eface struct {
_type *_type
data unsafe.Pointer
}
type iface struct {
tab *itab
data unsafe.Pointer
}
eface结构体
eface比较简单,代表的是没有方法的interface,interface{}
eface有两个属性,一个是_type
,一个是data
。data就是一个指针指向实际的值。type的结构如下
// $GOROOT/src/runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
// gcdata stores the GC type data for the garbage collector.
// If the KindGCProg bit is set in kind, gcdata is a GC program.
// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.
gcdata *byte
str nameOff
ptrToThis typeOff
}
type的意义就是表明interface所存储的值的具体类型(concrete type)。
iface结构体
iface也有两个属性,tab
和data
。data和eface中的是一样的。itab的结构如下
// $GOROOT/src/runtime/runtime2.go
type itab struct {
inter *interfacetype
_type *_type
hash uint32 // copy of _type.hash. Used for type switches.
_ [4]byte
fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}
// $GOROOT/src/runtime/type.go
type interfacetype struct {
typ _type
pkgpath name
mhdr []imethod
}
其中interfacetype是描述interface自身的类型,而_type字段和eface中的type类似,描述的是具体类型。fun是指向interface方法集的指针,go通过它实现Dynamic Dispatch。
看看反射(reflect)
顾名思义,反射中的reflect.TypeOf()
和reflect.ValueOf()
就对应interface的两个属性。它们的源码如下:
// $GOROOT/src/reflect/type.go
// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
// $GOROOT/src/reflect/value.go
// emptyInterface is the header for an interface{} value.
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i. ValueOf(nil) returns the zero Value.
func ValueOf(i interface{}) Value {
if i == nil {
return Value{}
}
// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)
return unpackEface(i)
}
// unpackEface converts the empty interface i to a Value.
func unpackEface(i interface{}) Value {
e := (*emptyInterface)(unsafe.Pointer(&i))
// NOTE: don't read e.word until we know whether it is really a pointer or not.
t := e.typ
if t == nil {
return Value{}
}
f := flag(t.Kind())
if ifaceIndir(t) {
f |= flagIndir
}
return Value{t, e.word, f}
}
先从TypeOf说起。Go的函数都是值传递,参数传入TypeOf的之前,会先被转换为interface{}类型。若传入参数自身为interface类型,则会把自身的具体类型(concret type)存储至新的eface结构体的_type字段之中。之后用unsafe.Pointer做强制类型转换,将eface结构体映射到emptyInterface上,这两个结构体拥有完全一样的结构。之后便可以从emptyInterface中获得类型的数据。这里面rtype的结构也必须和runtime中的_type完全一样。reflect.Type是一个interface,rtype实现了它的所有方法。返回的时候,go会把rtype结构体转换成reflect.Type返回。
再说回ValueOf方法。reflect.Value是个结构体。flag的部分先忽略掉。Value的结构其实就和eface很像了。从interface{}得到reflect.Value的过程也就比较直接。
翻译自官方Q&A
为什么类型T
不能满足Equal
interface
如下是一个简单的interface,可以用自身与另一个值进行比较:
type Equaler interface {
Equal(Equaler) bool
}
然后如下是类型T:
type T int
func (t T) Equal(u T) bool { return t == u } // 并不满足Equaler
与一些支持多态(polymorphic)的类型系统不一样,T并没有实现Equaler。T.Equal的参数类型是T,字面上并不是interface定义的Equaler类型。
在Go中,类型系统不会进一步判断Equal的参数的类型;就像如下实现了Equaler的类型T2所示,这是程序员的职责。
type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) } // 满足 Equaler
在Go中,因为任何满足Equaler的类型都可以作为参数传入T2.Equal,所以我们必须在运行时(run time)来检查参数的类型是否是T2. 一些语言在编译时(compile time)就可以确保这一点。
如下的例子从另一个侧面证明了这一点:
type Opener interface {
Open() Reader
}
func (t T3) Open() *os.File
在Go中, T3并没有满足Opener, 尽管在许多语言中,这是成立的。
尽管在这些例子中,Go的类型系统确实对程序员的帮助少了一些,但是舍弃掉子类型(subtyping)使得我们可以很容易说清interface的满足规则:是否方法的名字和签名和interface的完全一致。这样的规则也更容易实现。我们认为它的好处抵消了坏处。是否Go要在某一天实现某种形式的多态,我们会持续观察。
我可以把[]T
转换成[]interface{}
吗
无法直接做到。Go的语言规范不允许这样做,因为两种类型在内存中的表现是不同的。如果要转换的话,我们必须要把每个元素分别拷贝到目标切片。如下面例子所示:
t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
s[i] = v
}
为什么我的nil error不等于nil
只有当一个interface的V和T都unset时,它的值才是nil,换言之,一个nil interface的type一定是nil。如果我们把一个类型为*int的nil指针存放于一个interface中,这个变量的内部类型会是*int。这样的interface是非nil的,尽管它存储的值是nil。
当一个nil值被存储在一个interface中的时候就会发生这种不好理解的现象。一个典型的例子就是错误返回:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
这个方法永远会返回一个非nil的错误。想要向调用者正确的返回nil error,我们需要显式地返回nil:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
在函数签名的错误返回中使用error类型而不是使用具体类型比如*MyError会是一个好主意(如上所示),这能帮助确保错误是正确创建的。
写个小感想
interface的使用是要额外分配内存的,是会对性能产生影响的。当我们使用它的时候,主要是为了实现一定的灵活性,某种程度的范型,或者为了隐藏具体实现。在很关注性能的情况下,需要尽量少用interface,更要少用反射。 同样是 nil
,同样是.
,他们在涉及interface的时候是有不同的含义的。nil
很复杂,值得单写一篇文章专门去聊。而.
操作,在对应interface的时候,调用的实际方法是runtime才可以确定的。
- EOF -
Go 开发大全
参与维护一个非常全面的Go开源技术资源库。日常分享 Go, 云原生、k8s、Docker和微服务方面的技术文章和行业动态。
关注后获取
回复 Go 获取6万star的Go资源库
分享、点赞和在看
支持我们分享更多好文章,谢谢!