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

报告相同问题?

悬赏问题

  • ¥20 求快手直播间榜单匿名采集ID用户名简单能学会的
  • ¥15 DS18B20内部ADC模数转换器
  • ¥15 做个有关计算的小程序
  • ¥15 MPI读取tif文件无法正常给各进程分配路径
  • ¥15 如何用MATLAB实现以下三个公式(有相互嵌套)
  • ¥30 关于#算法#的问题:运用EViews第九版本进行一系列计量经济学的时间数列数据回归分析预测问题 求各位帮我解答一下
  • ¥15 setInterval 页面闪烁,怎么解决
  • ¥15 如何让企业微信机器人实现消息汇总整合
  • ¥50 关于#ui#的问题:做yolov8的ui界面出现的问题
  • ¥15 如何用Python爬取各高校教师公开的教育和工作经历