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
}