subject
Redhat Chief engineer of 、Prometheus Open source project Maintainer Bartłomiej Płotka stay Twitter There was a noise on the Go Programming questions , The result is more than 80% Everyone answered wrong .
The title is as follows , 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: Compiler error
- D: Recursive stack overflow
You can first think about the output of this code .
analysis
In function bbb
Finally, execute return sentence , The return value variable done
Assign a value ,
done := func() { print("bbb: surprise!"); done() }
Be careful : Closure func() { print("bbb: surprise!"); done() }
Inside done
It will not be replaced by done, err := aaa()
Inside done
Value .
So function bbb
After execution , Returns one of the values done
It actually becomes a recursive function , First print "bbb: surprise!"
, Then call yourself , This leads to infinite recursion , Until the stack overflows . So the answer to this question is D
.
Then why function bbb
Last return The closure of func() { print("bbb: surprise!"); done() }
Inside done
It will not be replaced by done, err := aaa()
Inside done
The value of ? If you replace , The answer to this question is B
了 .
At this time, an old saying will be put forward :
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()
// The following function calls will enter an endless loop , Keep printing test
done()
}
As the comments in the code above illustrate , This program will also enter infinite recursion until the stack overflows .
If the function test
Last return The closure of func() { fmt.Println("test"); done() }
Inside done
If it is parsed in advance , because done
Is a function type ,done
The zero value of is nil
, The one in the closure done
The value of will be nil
, perform nil
The function is to raise panic Of .
But actually Go The design allows the above code to execute normally , So function test
Last return In my closure done
The value of is not parsed in advance ,test
After function execution , In fact, it has the following effect , Returns a recursive function , 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 .
summary
This topic is actually very tricky, In actual programming , To avoid using this method for named return values , Very error prone .
Want to know about foreign countries Go For details of developers' discussion on this topic, please refer to Go Named Return Parameters Discussion.
In addition, the author also gives the following explanation , The original address can refer to Explain in detail :
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()
}
But this explanation is flawed , Mainly this sentence describes :
By using:=
and not=
we define a totally new variable with the same name in
new, local function scope.
about done, err := aaa()
, Return variable done
It's not a new variable , But with the function bbb
Return variable of done
Is the same variable .
Here's an episode : I feed back this flaw to the original author , The original author agreed with me , Delete this explanation .
The latest version of the English explanation is as follows , The original address can refer to Revised interpretation .
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 the named return value , You can see what the output of this question is . Can send messages nrv
Get 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())
}
Open source address
Articles and sample code are open source GitHub: Go Primary language 、 Intermediate and advanced tutorials .
official account :coding Advanced . Pay attention to the official account and get the latest information. Go Interview questions and technical stack .
Personal website :Jincheng's Blog.
You know : No taboo .