Go 1.13版本引入的bug,你遇到過這個坑麼?

coding進階發表於2022-05-14

Bug

大家看看下面這段程式,思考下輸出結果應該是什麼?

func main() {
  n := 0

  f := func() func(int, int) {
    n = 1
    return func(int, int) {}
  }
  g := func() (int, int) {
    println(n)
    return 0, 0
  }

  f()(g())
}

先思考幾秒鐘。。。


在Go 1.13版本之前,上述程式列印的結果是

1

在Go 1.13版本開始,上述程式列印的結果是

0

從習慣認知來看,編譯器應該按照如下順序執行

  • step 1: evaluate f(),此時變數n的值為1
  • step 2: evaluate g(),此時列印n時,因為step 1已經把n修改為1了,所以應該列印1
  • step3: 根據step 1和step 2的結果,執行最終的函式呼叫

Go編譯器其實也是遵循這個設計的,官方文件的描述如下:

At package level, initialization dependencies determine the evaluation order of individual initialization expressions in variable declarations.

Otherwise, when evaluating the operands of an expression, assignment, or return statement, all function calls, method calls, and communication operations are evaluated in lexical left-to-right order.

最後一句的表述可以知道,在執行函式呼叫時,對於函式的運算元,語法上是按照從左到右的順序進行解析的。

這個bug其實是Google Go團隊成員Matthew Dempsky提出來的,Go官方認領了這個bug,這個bug產生的原因也很明顯,就是先執行了g(),然後執行的f()

This is because we rewrite f()(g()) into t1, t2 := g(); f()(t1, t2) during type checking.

gccgo compiles it correctly.

@cuonglm Yeah, rewriting as t0 := f(); t1, t2 := g(); t0(t1, t2) instead seems like a reasonable quick fix to me. I think direct function calls and method calls are probably the most common case though, so we should make sure those are still handled efficiently.

Longer term, I think we should probably incorporate order.go directly into unified IR.

目前這個bug的影響範圍如下:

  • Go 1.13版本開始引入的這個bug,目前已經被修復,預計在1.19版本釋出。
  • 只有官方的編譯器gc有這個bug,如果你使用的是gccgo編譯器,也不會有這個問題。
  • 只對多個引數的函式呼叫才會有這個bug,比如下面這個例子f()的結果是一個函式,該函式只有1個引數,就不會有本文提到的這個bug。
package main

func main() {
    n := 0

    f := func() func(int) {
        n = 1
        return func(int) {}
    }
    g := func() int {
        println(n)
        return 0
    }

    f()(g())
}

上面程式執行結果是1

推薦閱讀

開源地址

文章和示例程式碼開源在GitHub: Go語言初級、中級和高階教程

公眾號:coding進階。關注公眾號可以獲取最新Go面試題和技術棧。

個人網站:Jincheng’s Blog

知乎:無忌

References

相關文章