键入断言接口,内部发生的事情

ber*_*eal 2 methods interface go type-assertion

我很好奇当Go执行类型断言而另一个接口是其目标时,内部会发生什么.仅举例来说,请考虑Dave Cheney的博客中的这个例子:

type temporary interface {
    Temporary() bool
}

// IsTemporary returns true if err is temporary.
func IsTemporary(err error) bool {
        te, ok := err.(temporary)
        return ok && te.Temporary()
}
Run Code Online (Sandbox Code Playgroud)

我希望在这里发生很多运行时开销,因为它必须检查类型err并找出它是否具有所有方法.是这样,还是有一些聪明的魔法在下面发生?

icz*_*cza 5

您描述的期望是有效的并且有效.运行时必须检查动态类型的方法集是否是要声明的接口类型的超集.

但不要害怕.这样做的实施得到了很大的优化(这是你的"智能魔法").

首先,函数类型由结构内部描述,其中方法签名(参数和结果类型)由称为签名id的单个整数值表示.如果2个函数具有相同的签名,则它们具有相同的签名ID.因此,为了比较2个函数(判断两个方法是否相同),运行时只需要比较名称(字符串比较)和签名ID(整数比较).

接下来,动态类型是否T实现了一个接口I仅检查/计算一次,并且结果被缓存.因此,即使在此检查中涉及一些工作,它也不会被执行多次,只执行一次,并且只要需要相同的类型检查(相同类型的断言),就会查找并使用缓存的结果.

因此,最终接口类型的类型断言归结为:(1)计算散列值(一些按位运算),(2)从映射中查找值,以及(3)构造结果接口值.

有关接口表示的介绍,请阅读Russ Cox:Go Data Structures:Interfaces.

这篇文章包含了上述所有细节:接口如何在Go中工作

例如,描述函数的相关部分是:

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 ...
}
Run Code Online (Sandbox Code Playgroud)

键入Assert To Interface Types:

以下是为接口类型断言接口值的内部函数:

// 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,
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

以下是_implementation从接口类型和非接口类型获取值的函数:

// 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
}
Run Code Online (Sandbox Code Playgroud)

  • 我还建议阅读[本文](https://research.swtch.com/interfaces).今天的Go不同于一点点-uintptr`大小的值不再直接嵌入到接口值中 - 但其余的仍然适用,并且IMO很好地解释了如何实现接口的想法. (2认同)