Go 接口实现原理高阶篇: type _interface struct
Posted 禅与计算机程序设计艺术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go 接口实现原理高阶篇: type _interface struct相关的知识,希望对你有一定的参考价值。
Go 接口实现原理【高阶篇】: type _interface struct
The Internal Definition Of Interface Types
https://www.tapirgames.com/blog/golang-interface-implementation
All interface types have the same internal definition:
type _interface struct
dynamicTypeInfo *_implementation
dynamicValue unsafe.Pointer // unsafe.Pointer means
// *ArbitraryType in Go.
The internal _implementation
type is declared like
type _implementation struct
itype *_type // the interface type.
dtype *_type // the dynamic type, which must implement itype.
methods []*_func // the methods which are defined on dtype
// and each of them implements a
// corresponding method declared in itype.
From the definitions, we know that each interface value contains two pointer fields. The dynamicValue
field stores the dynamic value information, and the dynamicTypeInfo
field stores the implementation information. dynamicTypeInfo.itype
stores the type information of the interface value and dynamicTypeInfo.dtype
stores the type information of the dynamic value.
The dynamicTypeInfo
field of an interface value may be nil
, which means nothing is stored in the interface value. For this case, the dynamicValue
field must be also nil
. We can also say the dynamic value of the interface value is untyped nil
for this case.
For the official Go compiler and runtime, a non-nil dynamicValue
field value may store
- the address of the dynamic value if the dynamic type is not a pointer type, or
- the dynamic value itself if the dynamic type is a pointer type.
Surely, it is not essential to make the exception for pointer dynamic values. This is just a compiler optimization. We can get why it is an optimization in following sections. (BTW, about more current and future optimizations in the official interface implementation, please read this article: http://commaok.xyz/post/interface-allocs/.)
Other involved internal types are declared like:
type _func struct
name string
methodSig uint // two methods with the same signature have
// the same signature id. Receiver parameter
// doesn't contribute to this signature.
funcSig uint // receiver parameter accounts to this signature.
// other information ...
type _type struct
name string // type name
id uint32 // each type has unique id
flags uint32 // comparable? isPointer?
size uintptr // value size in bytes
kind uint8 //
methods []*_func // the methods are sorted
// by methodSig and name.
// more information ...
const (
// flags
TypeFlag_Comparable = 1 << 0
TypeFlag_IsPointer = 1 << 1
TypeFlag_IsString = 1 << 2
)
func (t *_type) IsComparable() bool
return t.flags & TypeFlag_Comparable != 0
func (t *_type) IsPointer() bool
return t.flags & TypeFlag_IsPointer != 0
func (t *_type) IsString() bool
return t.flags & TypeFlag_IsString != 0
Some fields of above types are not listed, for they are unrelated to this article.
Here is the function to get an _implementation
value from an interface type and a non-interface type:
// global table
var cachedImpls = map[uint64]*_implementation
// itype must be an interface type and
// dtype must be a non-interface type.
// Return nil if dtype doesn't implement itype.
// Must not return nil if dtype implements itype.
func getImpl (itype *_type, dtype *_type) *_implementation
var key = uint64(itype.id) << 32 | uint64(dtype.id)
var impl = cachedImpls[key]
if impl == nil
// for each (dtype, itype) pair, the implementation
// method table is only calculated most once at
// run time. The calculation result will be cached.
var numMethods = len(itype.methods)
var methods = make([]*_func, numMethods)
// find every implemented methods.
// The methods of itype and dtype are both sorted
// by methodSig and name.
var n = 0
var i = 0
for _, im := range itype.methods
for i < len(dtype.methods)
tm := dtype.methods[i]
i++
// Here, for simplicity, assume
// all methods are exported.
if tm.methodSig < im.methodSig
continue
if tm.methodSig > im.methodSig
// im method is not implemented
return nil
if tm.name < im.name
continue
if tm.name > im.name
// im method is not implemented
return nil
methods[n] = tm
n++
break
// dtype doesn't implement all methods of itype
if n < numMethods
return nil
// dtype implements itype.
// create and cache the implementation.
impl = &_implementation
dtype: dtype,
itype: itype,
methods: methods,
cachedImpls[key] = impl
return impl
This function will be called in the value conversions explained in following sections.
In any Go program, at run time, all _implementation
values are cached and stored in a global map and all _type
values are stored in a global immutable array.
As the blank interface type interface
is used popular in Go programming, the official Go compiler uses a different and more efficient underlying definition for the blank interface than other interface types:
// blank interface
struct
dynamicType *_type // the dynamic type
dynamicValuePtr unsafe.Pointer // points to the dynamic value
To make the explainations simpler, following sections will treat blank interface types as general interface types.
Convert Non-Interface Values To Interface Types
Here is the internal function to convert a non-interface value to an interface type:
// To call this function, compilers must assure
// 1\\. itype is an interface type.
// 2\\. dtype is nil or a non-interface type and implements itype.
// p must be nil if dtype is nil.
// p is a pointer stores the address of a value of dtype if dtype
// is not a pointer type. p stores the value of a value of dtype
// if dtype is a pointer type.
func t2i (itype *_type, dtype *_type, p unsafe.Pointer) _interface
// dtype is nil means the non-interface value is untyped nil
if dtype == nil
return _interface
dynamicValue: nil,
dynamicTypeInfo: nil,
// the pointer dynamic value optimization, no need to
// allocate the extra memory block.
if dtype.IsPointer()
return _interface
dynamicValue: p,
dynamicTypeInfo: getImpl(dtype, itype),
// for non-pointer dynamic value, runtime must
// allocate an extra memory block to store a copy
// of the non-pointer value.
var t = memoryAlloc(dtype)
memoryCopy(t, p, dtype.size)
return _interface
dynamicValue: t,
dynamicTypeInfo: getImpl(dtype, itype),
Compilers will insert a call of this function before
- assigning a non-interface value to an interface value, to convert the non-interface value to the type of the interface value.
- comparing a non-interface value with an interface value, to convert the non-interface value to the type of the interface value.
Convert Interface Values To Other Interface Types
Here is the internal function to convert an interface value to an interface type:
// To call this function, compilers must assure
// 1\\. itype is an interface type.
// 2\\. the dynamic value of ivalue is untyped nil
// or the dynamic type of ivalue implements ivalue.
// (the method set of the dynamic type of ivalue must
// must be a super set of the method set of itype).
func i2i (itype *_type, ivalue _interface) _interface
// the dynamic value of ivalue is untyped nil.
if ivalue.dynamicTypeInfo == nil
return _interface
dynamicValue: nil,
dynamicTypeInfo: nil,
// <=> return ivalue
// compilers should avoid calling this function
// for this case.
if ivalue.dynamicTypeInfo.itype == itype
return ivalue // return a copy of ivalue.
// Convert the dynamic value of ivalue to itype.
// Here, the returned interface value and ivalue
// will share the same extra memory block which
// stores the dyanmic value if the dynamic value
// is not a pointer.
return _interface
dynamicValue: ivalue.dynamicValue,
dynamicTypeInfo: getImpl(
ivalue.dynamicTypeInfo.dtype,
itype,
), // here, the getImpl call never return nil.
Compilers will call this function before
- assigning an interface value to another interface value, to convert the first interface value to the type of the second interface value.
- comparing an interface value with another interface value, to convert the first interface value to the type of the second interface value.
Compilers should translate converting an interface value to its own type as a no-op.
Assign Interface Values
In an interface value assignment, the destination value must be an interface value, and the type of the source value must implement the destination interface type. The source value may be either a non-interface value or an interface value. As above two sections mentioned, compilers will convert the source value to the destination interface type before the assignment. So in the final assignment, the source value and the destination value have the same type, the destination interface type.
For the current official Go compiler/runtime, there are just two copies for the two fields, dynamicValue
and dynamicTypeInfo
, in the final assignment. So if the dynamic value is non-pointer, the underlying dynamic value memory block, which address is stored in the dynamicValue
field, will be shared between the destination and source interface values. However, this should be viewed as an optimization. Other compilers may not adopt this optimization.
Compare Interface Values
There are three comparison circumstances involving interface values:
- interface value vs. interface value.
- interface value vs. non-interface value.
- interface value vs. untyped
nil
.
A good compiler should treat the three circumstances differently to get better program performance. Here, for simplicity, we assume non-interface and untyped nil
values will be converted to interface
type before making the comparisons. So all comparisons involving interface values can be viewed as comparing two interface values.
Here is the internal function to compare two interface values:
func iCompare (ivalue1 _interface, ivalue2 _interface) bool
// untyped nil is only equal to untyped nil.
if ivalue1.dynamicTypeInfo == nil
return ivalue2.dynamicTypeInfo == nil
if ivalue2.dynamicTypeInfo == nil
return false
// panic on incomparable dynamic values.
if ! ivalue1.dynamicTypeInfo.dtype.IsComparable()
panic(ivalue1.dynamicTypeInfo.dtype.name +
" is incomparable")
if ! ivalue2.dynamicTypeInfo.dtype.IsComparable()
panic(ivalue2.dynamicTypeInfo.dtype.name +
" is incomparable")
// return false if dynamic type is not the same.
if ivalue1.dynamicTypeInfo.dtype !=
ivalue2.dynamicTypeInfo.dtype
return false
// optimization: early return.
if ivalue1.dynamicValue == ivalue2.dynamicValue
return true
// special case: string comparison
if ivalue1.dynamicTypeInfo.dtype.IsString()
return stringCompare(
*(*string)(ivalue1.dynamicValue),
*(*string)(ivalue2.dynamicValue),
)
// general case: compare all bytes in dynamic value
// memory blocks one by one.
return memoryCompare(
ivalue1.dynamicValue,
ivalue2.dynamicValue,
ivalue1.dynamicTypeInfo.dtype.size,
)
This article will not explain how two strings are compared.
Type Assert To Non-Interface Types
Here is the internal function to assert an interface value to a non-interface type:
// To call this function, compilers must assure
// 1\\. dtype is a non-interface type.
// 2\\. outP is nil or stores the address of a value of dtype.
// 3\\. outOk is nil or stores the address of a bool value.
func assertI2T (ivalue _interface, dtype *_type,
outP *unsafe.Pointer, outOk *bool)
// dynamic value is untype nil.
if ivalue.dynamicTypeInfo == nil
// if okay is not present, panic.
if outOk == nil
panic("interface is nil, not " + dtype.name)
// return (zero value, false)
*outOk = false
if outP != nil
if dtype.IsPointer()
*outP = nil
else
memoryReset(*outP, dtype.size)
return
// assersion fails.
if ivalue.dynamicTypeInfo.dtype != dtype
// if ok is not present, panic.
if outOk == nil
panic("interface is " +
ivalue.dynamicTypeInfo.dtype.name +
", not " + dtype.name)
// return (zero value, false)
*outOk = false
if outP != nil
if dtype.IsPointer()
*outP = nil
else
memoryReset(*outP, dtype.size)
return
// assersion succeeds.
if outOk != nil
*outOk = true
if outP == nil
return
// copy dynamic value.
if dtype.IsPointer()
*outP = ivalue.dynamicValue
else
memoryCopy(*outP, ivalue.dynamicValue, dtype.size)
Type Assert To Interface Types
Here is the internal function to assert an interface value to an interface type:
// To call this function, compilers must assure
// 1\\. itype is an interface type.
// 2\\. outI is nil or stores the address of a value of itype.
// 3\\. outOk is nil or stores the address of a bool value.
func assertI2I (ivalue _interface, itype *_type,
outI *_interface, outOk *bool)
// dynamic value is untype nil.
if ivalue.dynamicTypeInfo == nil
// if ok is not present, panic.
if outOk == nil
panic("interface is nil, not " + itype.name)
*outOk = false
if outI == nil
*outI = _interface
dynamicValue: nil,
dynamicTypeInfo: nil,
return
// check whether or not the dynamic type implements itype
var impl = getImpl(itype, ivalue.dynamicTypeInfo.dtype)
// assersion fails.
if impl == nil
// if ok is not present, panic.
if outOk == nil
panic("interface is " +
ivalue.dynamicTypeInfo.dtype.name +
", not " + itype.name)
// return (zero value, false)
*outOk = false
if outI != nil
*outI = _interface
dynamicValue: nil,
dynamicTypeInfo: nil,
return
// assersion succeeds.
if outI == nil
*outOk = true
if outI != nil
*outI = _interface
dynamicValue: ivalue.dynamicValue,
dynamicTypeInfo: impl,
If the type of the interface value is the asserted interface type, compilers should simply return the interface value.
Call Interface Methods
For an interface value i
, a call of its nth method (by the order after sorted)
... = i.Method_n(...)
will be translated to
if i.dynamicTypeInfo == nil
panic("runtime error: nil pointer dereference")
if i.dynamicTypeInfo.dtype.IsPointer()
... = _call(i.dynamicTypeInfo.methods[n], i.dynamicValue, ...)
else
... = _call(i.dynamicTypeInfo.methods[n],
*(*unsafe.Pointer)(i.dynamicValue), ...)
The interfacetype
structure
Finally, here's the interfacetype
structure (src/runtime/type.go):
type interfacetype struct // 80 bytes on a 64bit arch
typ _type
pkgpath name
mhdr []imethod
type imethod struct
name nameOff
ityp typeOff
As mentioned, an interfacetype
is just a wrapper around a _type
with some extra interface-specific metadata added on top.
In the current implementation, this metadata is mostly composed of a list of offsets that points to the respective names and types of the methods exposed by the interface ([]imethod
).
Conclusion
Here's an overview of what an iface
looks like when represented with all of its sub-types inlined; this hopefully should help connect all the dots:
type iface struct // `iface`
tab *struct // `itab`
inter *struct // `interfacetype`
typ struct // `_type`
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
pkgpath name
mhdr []struct // `imethod`
name nameOff
ityp typeOff
_type *struct // `_type`
size uintptr
ptrdata uintptr
hash uint32
tflag tflag
align uint8
fieldalign uint8
kind uint8
alg *typeAlg
gcdata *byte
str nameOff
ptrToThis typeOff
hash uint32
_ [4]byte
fun [1]uintptr
data unsafe.Pointer
learn more:https://github.com/teh-cmc/go-internalshttps://github.com/teh-cmc/go-internals/blob/master/chapter2_interfaces/README.mdhttps://research.swtch.com/interfaceshttps://go.dev/doc/effective_go#interfaces_and_typeshttps://www.tapirgames.com/blog/golang-interface-implementation
以上是关于Go 接口实现原理高阶篇: type _interface struct的主要内容,如果未能解决你的问题,请参考以下文章
[从零开始学习FPGA编程-53]:高阶篇 - 基于IP核的FPGA开发-PLL锁相环IP核的原理与配置(Xilinx)
[从零开始学习FPGA编程-54]:高阶篇 - 基于IP核的FPGA开发-PLL锁相环IP核的原理与配置(Altera)