go version 1.14.12
何为 Duck Type
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
Show me the code 🤔
type Duck interface {
Quack()
}
type Cat struct{}
//The Cat implements the Quack() method, now, the Cat is Duck Type
func (c Cat) Quack() {
fmt.Println("Miowo")
}
func main() {
var _ Duck = Cat{}
}
得意之时,Cat
突然尴尬地说,我好像不会Quack
这个技能,但是我会Quacks
😁,您看这也差不多,我也能算鸭子吧?
编译器盘算了一番,无情地划了个 ❌
问题分析
- 问题定位:出现在编译过程中,查看compile包。
- 报错定位:在compile包全局搜索报错信息关键字段
does not implement
。 - 筛选一下,找到了
subr.go
很好,我们定位了:*why = fmt.Sprintf(":\n\t%v does not implement %v (missing %v method)", src, dst, missing.Sym)
函数内上下文发现(注意代码注释):
// 3. dst is an interface type and src implements dst.
if dst.IsInterface() && src.Etype != TNIL {
var missing, have *types.Field
var ptr int
// 判断src是否实现了dst
if implements(src, dst, &missing, &have, &ptr) {
return OCONVIFACE
}
//....省略
}
不过,src
是啥,dst
又是啥?
看来还是得了解下程序编译流程。步骤如下:
- 词法和语法分析
- 类型检查
- 中间代码生成
- 机器码生成
很明显,我们的问题在类型检查这一阶段。
而src,dst
对应的数据结构 *types.Type
,查看数据结构中的 nod
,和 gc.Node
有所关联。
而类型检查这一阶段,已经完成了抽象语法树的生成。
那我们看看go在编译过程中 var _ Duck = Cat{}
, 到底经历了什么?让它无法通过implements()
通过我们IDE的Debug工具,可以确定代码流程如下:
gc.Main -> typecheckslice-> typecheck ->typecheck1 -> typecheckas ->assignconv-> assignconvfn ->assignop -> implements
代码逻辑思路如下:
graph TD
A[开始编译] -->|词法和语法分析器| B(抽象语法树)
B --> C(遍历类型检查func body)
C --> D(当Op是赋值语句)
D --> E(typecheckas)
E --> |n.Right , n.Left.Type|F(implements)
看一看 n.Right, n.Left.Type
的结构,嗯,看起来有点复杂,不过我们只关注需要关注的字段
n.Right => Cat Struct
n.Left.Type => Duck Type
源码分析
package gc
func implements(t, iface *types.Type, m, samename **types.Field, ptr *int) bool {
t0 := t
if t == nil {
return false
}
//......省略里部分代码
t = methtype(t)
var tms []*types.Field
if t != nil {
expandmeth(t)
tms = t.AllMethods().Slice()
}
i := 0
//遍历 Duck 里的方法
for _, im := range iface.Fields().Slice() {
if im.Broke() {
continue
}
// i 是 Cat 和 Duck 中method 不一样的数量
for i < len(tms) && tms[i].Sym != im.Sym {
i++
}
//如果Cat和Duck中的不一样的method数量 等于 Cat中method总数量,明显说明 Cat没有实现Duck
//举个例子,A数组里有3个元素,B数组里有2个元素,如果A和B不同的元素数量 等于 A自己元素的数量,那么A肯定没有一个元素和B相同,也就是说...A没有实现B
if i == len(tms) {
*m = im
*samename, _ = ifacelookdot(im.Sym, t, true)
*ptr = 0
return false
}
tm := tms[i]
if tm.Nointerface() || !types.Identical(tm.Type, im.Type) {
*m = im
*samename = tm
*ptr = 0
return false
}
followptr := tm.Embedded == 2
//......省略里部分代码
}
// We're going to emit an OCONVIFACE.
// Call itabname so that (t, iface)
// gets added to itabs early, which allows
// us to de-virtualize calls through this
// type/interface pair later. See peekitabs in reflect.go
if isdirectiface(t0) && !iface.IsEmptyInterface() {
itabname(t0, iface)
}
return true
}
以上,就是编译器判断Cat
是不是 duck type
的大致流程,还有更多细节大家有兴趣可以自行源码探究,希望我们探究原理的历程思路能给大家带来一些启发。
Debug方法
Debug关键流程截图如下:
1.查看文件目录
2.Goland打开 src
源码,修改Debug处运行配置,开始Debug
3.将光标放在代码可运行到的位置,点击运行到光标处
,查看函数调用流程
Goland Debug的可视化,极大的便利了我们查看代码运行流程的细节,希望大家可以充分利用。