While surfing recently, I saw Bartłomiej Płotka, chief software engineer of Redhat and maintainer of Prometheus and other projects, posted on twitter A Go "exam question" is meant to test everyone.
The questions are as follows:
func aaa() (done func(), err error) { return func() { print("aaa: done") }, nil } func bbb() (done func(), _ error) { done, err := aaa() return func() { print("bbb: surprise!"); done() }, err } func main() { done, _ := bbb() done() }
The answer options are as follows:
##C: Keep outputting and never end
##
想想原因,思考一下输出结果是什么?是 A,还是 D?还是三短一长,选 B?
缩小范围,核心关注到这块代码。如下:
func bbb() (done func(), _ error) { done, err := aaa() return func() { print("bbb: surprise!") done() }, err }
在最后一行的这个闭包(匿名函数)中,大家可能认为程序调用了函数aaa
所返回的done
值来输出程序,应当是:
aaa: done
这个想法是错误的,程序没有这么去运作。
原因在于return
实际上是一个赋值语句。结合程序,可以看到函数bbb
的第一个返回值是done
参数。
如下:
func bbb() (done func(), _ error)
也就是在函数bbb
在程序最后执行return
语句后,会对返回变量done
进行赋值,自然该值不会是由函数aaa
所设置的了。
这是一个关键的地方。
这个程序输出结果是什么呢?
他会不断地递归,疯狂输出 “bbb: surprise!”,直至栈溢出,导致程序运行出错,最终中止
同学就疑惑了,怎么又多出了个递归?
我们再看看程序:
func main() { done, _ := bbb() done() } func bbb() (done func(), _ error) { ... return func() { print("bbb: surprise!"); done() }, err }
本质上在函数bbb
执行完毕后, 变量done
已经变成了一个递归函数。
递归的过程是:函数bbb
调用变量done
后,会输出bbb: surprise!
字符串,然后又调用变量done
。而变量done
又是这个闭包(匿名函数),从而实现不断递归调用和输出。
最终结果如下:
b: surprise!bbb: surprise!bbb: surprise!runtime: goroutine stack exceeds 1000000000-byte limit runtime: sp=0xc0200e0380 stack=[0xc0200e0000, 0xc0400e0000] fatal error: stack overflow runtime stack: runtime.throw(0x1074b5a, 0xe) /usr/local/Cellar/go/1.16.6/libexec/src/runtime/panic.go:1117 +0x72 runtime.newstack() /usr/local/Cellar/go/1.16.6/libexec/src/runtime/stack.go:1069 +0x7ed runtime.morestack() /usr/local/Cellar/go/1.16.6/libexec/src/runtime/asm_amd64.s:458 +0x8f ...
也就是正确答案是:D,程序最终运行出错。
一直调用一直爽,直至栈溢出程序崩溃。
这位大佬出的题目,本质上是比较烦人的,其结合了函数返回参数的命名用法。
如果我们把这个函数的返回参数命名去掉,就可以避开这个问题。如下:
func bbb() (func(), error) { ... return func() { print("bbb: surprise!"); done() }, err } ...
输出结果为 "bbb: surprise!"。
很多 Go 的同学在日常代码编写的时候不会用到或注意到。但如果写的时候有类似案例代码中的模式,就会排查许久都查不到。
这是个有警惕意义的题目,你觉得呢?
The above is the detailed content of Can you get this Go question right? More than 80% of people got it wrong.... For more information, please follow other related articles on the PHP Chinese website!