Go1.20 將會修改全局變量的初始化順序。梅度二開,繼續(xù)打破 Go1 兼容性承諾!
大家好,我是煎魚。
Go1.20 已經(jīng)發(fā)布了 rc1,大家都關(guān)注了一些大頭的功能特性,例如:PGO、Arean 等,都沒有那么的常接觸到。
實質(zhì)上本次新版本還修復(fù)了在全局變量初始化方面的順序,來自《cmd/compile: global variable initialization done in unexpected order[1]》,這是個挺有趣的問題。
神奇案例
從案例展開,假設(shè)在同一個 package 下有 2 個文件,分別是:f1.go 和 f2.go,包含了不同的包全局變量聲明和代碼。
文件 f1.go。代碼如下:
文件 f2.go。代碼如下:
問題來了。
如果運行 go run f1.go f2.go,會輸出什么結(jié)果?
運行結(jié)果如下:
你答對了嗎?再仔細想想。
如果運行 go run f2.go f1.go,會輸出什么結(jié)果?
運行結(jié)果如下:
這只是 run 的文件先后順序不一樣了,咋就連輸出的結(jié)果都不一樣了?
輸出結(jié)果到底誰對誰錯,還是說都錯了,正確的是什么?
Go 規(guī)范定義
我們要知道正確輸出的結(jié)果是什么,還得是看 Go 語言規(guī)范《The Go Programming Language Specification[2]》說了算。
sepc
在規(guī)范中的包初始化(Package initialization)章節(jié)中明確指出:"在一個包中,包級別的變量初始化是逐步進行的,每一步都會選擇聲明順序中最早的變量,它不依賴于未初始化的變量。"
更完整和準確的闡述:
- 如果包級變量尚未初始化并且沒有初始化表達式或其初始化表達式不依賴于未初始化的變量,則認為包級變量已準備好進行初始化。
- 初始化通過重復(fù)初始化聲明順序中最早并準備初始化的下一個包級變量來進行,直到?jīng)]有變量準備好進行初始化。
在了解了理論知識后,我們再結(jié)合官方例子看看,加強實踐的補全。
例子 1。代碼如下:
在初始化變量 x 之前,變量 a 和 b 會一起初始化(在同一步驟中)。
例子 2。代碼如下:
初始化順序是:d, b, c, a。
案例哪里有問題
在解讀了背景和規(guī)范后,再次回顧文章剛開始的案例。
文件 f1.go。代碼如下:
文件 f2.go。代碼如下:
第一種,運行 go run f1.go f2.go,輸出:1 4 3。
第二種,運行 go run f2.go f1.go,輸出:1 2 3.
如果按照規(guī)范來,分析程序變量初始化順序和應(yīng)該輸出的結(jié)果。如下:
- 第一種的順序是 A < B < C < D:發(fā)生在你編譯項目時,運行命令先把 f1.go 傳給編譯器,然后再傳 f2.go。在這種情況下,輸出結(jié)果是 1 4 3。
- 第二種的順序是 A < D < B < C:發(fā)生在先將 f2.go 傳給編譯器時。在這種情況下,預(yù)期輸出是 1 2 1。然而,實際的輸出是 1 2 3。
問題出在第二種情況,我們嘗試改一下寫法,變成如下代碼:
輸出結(jié)果:
預(yù)期結(jié)果就一致了。
上一個案例輸出 1 2 3,這是有 BUG!與 Go 規(guī)范定義的不一致。
修復(fù)時間
目前這個問題已經(jīng)明確是 Go 編譯/運行時的 BUG,并且這個問題已經(jīng)存在了很久,將計劃在 Go1.20 中修復(fù)。
不過由于不知道是否會影響用戶,因此 Go 官方將會更多的關(guān)注社區(qū)反饋。
當然,這個確實是 BUG,會修。也為此認為值得打破 Go1 兼容性的原則。
總結(jié)
今天這篇文章我們介紹了 Go 一直以來存在的一個 Go 編譯/運行時的 BUG,會導(dǎo)致 Go 程序的全局變量會與 Go 規(guī)范本身定義的不一致,將預(yù)計會在 Go1.20 修復(fù)。
這也是 Go 打破 Go1 兼容性承諾的又一個案例。值得我們關(guān)注。