dtk31564 2018-06-21 06:34
浏览 65
已采纳

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

I am curious about what happens internally when Go performs type assertion with another interface being its destination. Just for sake of example, consider this example from 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()
}

I would expect quite a lot of runtime overhead to happen here, since it has to inspect the type of err and find whether it has all the methods in place. Is that so, or is there some smart magic happening underneath?

  • 写回答

1条回答 默认 最新

  • duanmianhong4893 2018-06-21 06:56
    关注

    The expectations you described are valid and hold. The runtime has to check if the method set of the dynamic type is a superset of the interface type you want to assert to.

    But fear not. Implementation to do this is heavily optimized (this is your "smart magic").

    For a start, function types are internally described by a struct, where the method signature (parameter and result types) are represented by a single integer value called the signature id. If 2 functions have the same signature, they have the same signature id. So to compare 2 functions (to tell if 2 methods are the same) the runtime only has to compare the names (string comparison) and signature ids (integer comparison).

    Next, whether a dynamic type T implements an interface I is only checked / calculated once, and the result is cached. So even though there is some work involved in this check, it will not be executed multiple times, just once, and whenever the same type check is needed (the same type assertion), the cached result will be looked up and used.

    So ultimately what a type assertion to interface type boils down to is: (1) calculate a hash value (some bitwise operations), (2) look up a value from a map and (3) construct the result interface value.

    For an introduction about interface representation, read Russ Cox: Go Data Structures: Interfaces.

    Here is an article which holds all the details about the above: How interfaces work in Go

    For example the relevant part to describe a function is:

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

    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
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 BP神经网络控制倒立摆
  • ¥20 要这个数学建模编程的代码 并且能完整允许出来结果 完整的过程和数据的结果
  • ¥15 html5+css和javascript有人可以帮吗?图片要怎么插入代码里面啊
  • ¥30 Unity接入微信SDK 无法开启摄像头
  • ¥20 有偿 写代码 要用特定的软件anaconda 里的jvpyter 用python3写
  • ¥20 cad图纸,chx-3六轴码垛机器人
  • ¥15 移动摄像头专网需要解vlan
  • ¥20 access多表提取相同字段数据并合并
  • ¥20 基于MSP430f5529的MPU6050驱动,求出欧拉角
  • ¥20 Java-Oj-桌布的计算