接口的类型断言,内部发生什么

时间:2018-06-21 06:34:06

标签: go methods interface type-assertion

我很好奇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的类型并确定它是否具有所有方法。是这样吗,还是在下面发生了一些聪明的魔术?

1 个答案:

答案 0 :(得分:5)

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

但不要害怕。为此,实施已进行了优化(这是您的“智能法宝”)。

首先,函数类型由结构内部描述,其中方法签名(参数和结果类型)由称为 signature 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
}