Bartłomiej Płotka, chief software engineer at Redhat and maintainer of projects such as Prometheus, asked a Go question on twitter with the following title.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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()
}

Think about what the output will be.

Analyze the program

Narrow the scope and core focus to this piece of code. As follows.

1
2
3
4
5
6
7
func bbb() (done func(), _ error) {
 done, err := aaa()
 return func() { 
   print("bbb: surprise!")
   done() 
 }, err
}

In this last line of the closure (anonymous function), one might think that the program calls the done value returned by the function aaa to output the program, which should be:

1
aaa: done

This idea is wrong, the program does not work that way.

The reason is that return is actually an assignment statement. Combined with the program, you can see that the first return value of function bbb is the done argument.

As follows.

1
func bbb() (done func(), _ error)

That is, after the function bbb executes the return statement at the end of the program, it will assign a value to the return variable done, which naturally will not be set by the function aaa.

This is a key point.

The process

What is the output of this program?

He will keep recursively outputting “bbb: surprise!” until the stack overflows , causing the program to run with errors and eventually abort!

Here comes the confusion, how come there is an additional recursion?

Let’s look at the program again.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
 done, _ := bbb()
 done()
}

func bbb() (done func(), _ error) {
 ...
 return func() { 
   print("bbb: surprise!"); 
   done() 
 }, err
}

Essentially, after the function bbb is executed, the variable done has become a recursive function.

The recursive process is that the function bbb calls the variable done, which outputs the bbb: surprise! string, and then calls the variable done. The variable done is in turn this closure (anonymous function), thus enabling constant recursive calls and output.

The end result is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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
...

That is, the correct answer is: D. The program eventually runs in error.

If we remove the return parameter naming from this function, we can avoid this problem. As follows.

1
2
3
4
5
6
7
8
func bbb() (func(), error) {
 ...
 return func() { 
   print("bbb: surprise!"); 
   done() 
 }, err
}
...

The output is “bbb: surprise!aaa: done”.