当前位置:网站首页>Go quiz: considerations for function naming return value from the go interview question (more than 80% of people answered wrong)

Go quiz: considerations for function naming return value from the go interview question (more than 80% of people answered wrong)

2022-06-25 05:47:00 Coding advanced

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 .

References

原网站

版权声明
本文为[Coding advanced]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202201416575111.html