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-internals
https://github.com/teh-cmc/go-internals/blob/master/chapter2_interfaces/README.md
https://research.swtch.com/interfaces
https://go.dev/doc/effective_go#interfaces_and_types
https://www.tapirgames.com/blog/golang-interface-implementation

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容