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 深度学习根据CNN网络模型,搭建BP模型并训练MNIST数据集
  • ¥15 lammps拉伸应力应变曲线分析
  • ¥15 C++ 头文件/宏冲突问题解决
  • ¥15 用comsol模拟大气湍流通过底部加热(温度不同)的腔体
  • ¥50 安卓adb backup备份子用户应用数据失败
  • ¥20 有人能用聚类分析帮我分析一下文本内容嘛
  • ¥15 请问Lammps做复合材料拉伸模拟,应力应变曲线问题
  • ¥30 python代码,帮调试,帮帮忙吧
  • ¥15 #MATLAB仿真#车辆换道路径规划
  • ¥15 java 操作 elasticsearch 8.1 实现 索引的重建