The title

Maintainer Bartłomiej Płotka, lead engineer at Redhat, Prometheus open source project Maintainer Bartłomiej Płotka, created a Go programming question on Twitter, and more than 80% of people got it wrong.

Answer the output of the program shown below.

// named_return.go package main import "fmt" 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() }Copy the code
  • A: bbb: surprise!
  • B: bbb: surprise! aaa: done
  • C: Compilation error occurs
  • D: Recursive stack overflow

You can think about what the output of this code is going to be.

parsing

The return statement at the end of BBB assigns the return variable done,

done := func() { print("bbb: surprise!" ); done() }Copy the code

Closure func() {print(” BBB: surprise!” ); Done ()} is not replaced by done, err := aaa().

So once BBB is done, one of the returns is actually a recursive function that prints “BBB: Surprise!” “, and then call itself, so you get stuck in infinite recursion until the stack overflows. D.

Func () {print(” BBB: surprise!” ); Done ()} is not replaced by done. Err := aaa() And if we do, the answer to this question is B.

Here’s an old saying:

This is a feature, not a bug

Let’s look at a simpler example to help us understand:

// named_return1.go package main import "fmt" func test() (done func()) { return func() { fmt.Println("test"); Done ()} func main() {done := test()}Copy the code

As noted in the code above, this program also goes into infinite recursion until the stack overflows.

If func() {fmt.println (“test”); If done in done()} is parsed ahead of time, since done is a function type variable and the value of done is nil, the value of done in the closure will be nil, and executing nil will cause panic.

So the done value in the closure of the return of test is not parsed ahead of time. Test actually returns a recursive function. Same title as at the beginning of this article.

done := func() { fmt.Println("test"); done() }
Copy the code

So it goes into infinite recursion until the stack overflows.

conclusion

This topic is tricky, and in practical programming you need to avoid writing a named return value in this way, which can be very error-prone.

For more details about the Discussion on this topic by foreign Go developers, please refer to Go Named Return Parameters Discussion.

In addition, the author also gave the following explanation, the original address can refer to the detailed explanation:

package main func aaa() (done func(), err error) { return func() { print("aaa: done") }, nil } func bbb() (done func(), _ error) { // NOTE(bwplotka): Here is the problem. We already defined special "return argument" variable called "done". // By using `:=` and not `=` we define a totally new variable with the same name in // new, local function scope. done, err := aaa() // NOTE(bwplotka): In this closure (anonymous function), we might think we use `done` from the local scope, // but we don't! This is because Go "return" as a side effect ASSIGNS returned values to // our special "return arguments". If they are named, this means that after return we can refer // to those values with those names during any execution after the main body of function finishes // (e.g in defer or closures we created). // // What is happening here is that no matter what we do  in the local "done" variable, the special "return named" // variable `done` will get assigned with whatever was returned. Which in bbb case is this closure with // "bbb:surprise" print. This means that anyone who runs this closure AFTER `return` did the assignment // will start infinite recursive execution. // // Note that it's a feature, not a bug. We use this often to capture // errors (e.g https://github.com/efficientgo/tools/blob/main/core/pkg/errcapture/doc.go) // // Go compiler actually detects that `done` variable defined above is NOT USED. But we also have `err` // variable which is actually used. This makes compiler to satisfy that unused variable check, // which is wrong in this context.. return func() { print("bbb: surprise!" ); done() }, err } func main() { done, _ := bbb() done() }Copy the code

But this explanation is flawed, mainly in this description:

By using := and not = we define a totally new variable with the same name in

new, local function scope.

For done, err := aaa(), the return variable done is not a new variable, but the same variable as BBB’s return variable done.

Here’s an interlude: I reported this flaw to the original author, who agreed with me and deleted the explanation.

The latest version of the English explanation below, the original address can refer to the revised version of the explanation.

package main func aaa() (done func()) { return func() { print("aaa: done") } } func bbb() (done func()) { done = aaa() // NOTE(bwplotka): In this closure (anonymous function), we might think we use `done` value assigned to aaa(), // but we don't! This is because Go "return" as a side effect ASSIGNS returned values to // our special "return arguments". If they are named, this means that after return we can refer // to those values with those names during any execution after the main body of function finishes // (e.g in defer or closures we created). // // What is happening here is that no matter what we do  with our "done" variable, the special "return named" // variable `done` will get assigned with whatever was returned when the function ends. // Which in bbb case is this closure with "bbb:surprise" print. This means that anyone who runs // this closure AFTER `return` did the assignment, will start infinite recursive execution. // // Note that it's a feature, not a bug. We use this often to capture // errors (e.g https://github.com/efficientgo/tools/blob/main/core/pkg/errcapture/doc.go) return func() { print("bbb: surprise!" ); done() } } func main() { done := bbb() done() }Copy the code

To consider

The following code also uses the named return value, so you can see what the output of this problem is. You can send a message to the VX public account NRV for answers.

package main func bar() (r int) { defer func() { r += 4 if recover() ! = nil { r += 8 } }() var f func() defer f() f = func() { r += 2 } return 1 } func main() { println(bar()) }Copy the code

Open source address

Articles and sample code open source at GitHub: Go language beginner, Intermediate, and advanced tutorials.

Public id: coding advanced.

Personal website: Jincheng’s Blog.

References

  • twitter.com/bwplotka/.. …
  • Go. Dev/play/p/ELPE…
  • Go. Dev/play/p / 9 j5a…