对接口进行类型断言,内部发生了什么

Type assertion to interfaces, what happens internally

我很好奇当 Go 以另一个接口作为目标执行类型断言时,内部会发生什么。举个例子,考虑这个来自 Dave Cheney's blog:

的例子
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()
}

我预计这里会发生相当多的运行时开销,因为它必须检查 err 的类型并确定它是否具有所有方法。是这样吗,还是背后有什么聪明的魔法在发生?

您描述的期望是有效的并且成立。运行时必须检查动态类型的 method set 是否是您要断言的接口类型的超集。

但不要害怕。执行此操作已经过大量优化(这是您的 "smart magic")。

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

接下来,动态类型T是否实现接口I只检查/计算一次,并将结果缓存起来。所以虽然这个检查涉及到一些工作,但不会执行多次,只会执行一次,每当需要进行同类型检查(同类型断言)时,就会查找并使用缓存的结果。

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

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

这是一篇包含上述所有详细信息的文章:How interfaces work in 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 ...
}

类型断言接口类型:

这是将接口值声明为接口类型的内部函数:

// 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,
        }
    }
}

下面是从接口类型和非接口类型获取_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
}