topic
Redhat's chief engineer, Prometheus open source project Maintainer Bartłomiej Płotka a Go programming question on Twitter, and more than 80% of the people answered it wrong.
The questions are as follows, and answer the output of the following program.
// 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()
}
- A:
bbb: surprise!
- B:
bbb: surprise!aaa: done
- C: compile error
- D: recursion stack overflow
You can first think about what the output of this code is.
Parse
When the return statement is executed at the end of the function bbb
, the return value variable done
will be assigned,
done := func() { print("bbb: surprise!"); done() }
Note : func() { print("bbb: surprise!"); done() }
in the closure done
is not replaced with the value of done, err := aaa()
in done
.
Therefore, after the function bbb
executed, one of the return values, done
, actually becomes a recursive function. It prints "bbb: surprise!"
first, and then calls itself, which will fall into infinite recursion until the stack overflows. So the answer to this question is D
.
Then why is the value of 0621717e315b61 in the closure func() { print("bbb: surprise!"); done() }
by the done
bbb
replaced with the value of done, err := aaa()
in done
? If replaced, the answer to this question is B
.
At this time, it is time to move out an old saying:
This is a feature, not a bug
We can look at the following 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()
// 下面的函数调用会进入死循环,不断打印test
done()
}
As the comments in the code above indicate, this program will also enter infinite recursion until the stack overflows.
If the function test
closure last return of func() { fmt.Println("test"); done() }
in the done
be resolved in advance, because done
is a function type, done
zero value is nil
, then closed the bag done
value would be nil
, execution nil
function is will cause panic.
But in fact, the Go design allows the above code to be executed normally, so the value of done
in the closure of the final return of the function test
will not be parsed in advance. After the execution of the test
function, the following effect is actually produced, and the return is A recursive function, the same as the title at the beginning of this article.
done := func() { fmt.Println("test"); done() }
Therefore, it will also enter infinite recursion until the stack overflows.
Summarize
This topic is actually very tricky. In actual programming, it is very error-prone to avoid using this way of writing named return values.
If you want to know more about the discussion of this topic by foreign Go developers, please refer to Go Named Return Parameters Discussion .
In addition, the author of the title also gave the following explanation. The original address can refer to detailed explanation of :
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()
}
However, this explanation is flawed, mainly 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
not a new variable, but the same variable as the return variable done
of the function bbb
.
Here is a small episode: I reported this flaw to the original author, and the original author agreed with my opinion and deleted this explanation .
The English explanation of the latest version is as follows, the original address can refer to the revised version of to explain .
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()
}
thinking questions
The following code also uses named return values. You can see what the output of this question is. You can send a message to the WeChat public nrv
to get the answer.
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())
}
open source address
The article and sample code are open sourced on GitHub: Go language beginner, intermediate and advanced tutorial .
Official account: coding advanced. Follow the official account to get the latest Go interview questions and technology stacks.
Personal website: Jincheng's Blog .
Know: Wuji .
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。