在go反射的秘密一文中,我从源代码的角度分析了go反射的相关细节,但是在那篇文章中,还是留下了两个疑问:
- 为什么
TypeOf
函数的实现是直接强制类型转换? - 一个具体类型的变量是如何转成
interface
的?
由于当时的我年少无知,没能解答出这两个问题。时过境迁,再次读书的时候偶然明悟,遂作此文以解惑。
让我们再次回顾一下TypeOf
函数的源码:
/*--type.go--*/
func TypeOf(i interface{}) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
}
/* ... ... */
type emptyInterface struct {
typ *rtype
word unsafe.Pointer
}
其实从源码中我们也能大概推测是事情的原因,那就是interface
类型和emptyInterface
类型必然有着相同的数据结构!只是当时没能想到,也就失去了求证的机会。
在go源码runtime包的runtime2.go中,有如下代码:
// 非空接口类型
type iface struct {
tab *itab
data unsafe.Pointer
}
// 空接口类型
type eface struct {
_type *_type
data unsafe.Pointer
}
可以看到emptyInterface
和eface
的定义是如此的相似,以至于让我们迫不及待的想去看看rtype
和_type
的定义是否也相同。
// rtype is the common implementation of most values.
// It is embedded in other struct types.
//
// rtype must be kept in sync with ../runtime/type.go:/^type._type.
type rtype struct {
size uintptr
ptrdata uintptr // number of bytes in the type that can contain pointers
hash uint32 // hash of type; avoids computation in hash tables
tflag tflag // extra type information flags
align uint8 // alignment of variable with this type
fieldAlign uint8 // alignment of struct field with this type
kind uint8 // enumeration for C
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte // garbage collection data
str nameOff // string form
ptrToThis typeOff // type for pointer to this type, may be zero
}
// Needs to be in sync with ../cmd/link/internal/ld/decodesym.go:/^func.commonsize,
// ../cmd/compile/internal/gc/reflect.go:/^func.dcommontype and
// ../reflect/type.go:/^type.rtype.
// ../internal/reflectlite/type.go:/^type.rtype.
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
惊喜单不意外的是,它们两的确是一模一样,甚至注释中还贴心的写着它们必须保持同步。至于TypeOf
函数直接强转为emptyInterface
类型,那是因为TypeOf
函数的参数就是空接口。
现在的问题就是一个具体类型的值是如何转换为iface
或eface
的,这其实是编译器帮我们实现的,其中的字段也是由编译器进行填充的。