注意:Go 1.18版本iota的bug

coding進階發表於2022-05-03

Iota

iota是Go語言的預宣告識別符號,用於常量的宣告。

iota的值是const語句塊裡的行索引,值從0開始,每次遞增加1。通過下面的程式碼示例我們先回顧下iota的特性。

const (
    c0 = iota  // c0 == 0
    c1 = iota  // c1 == 1
    c2 = iota  // c2 == 2
)

const (
    a = 1 << iota  // a == 1  (iota == 0)
    b = 1 << iota  // b == 2  (iota == 1)
    c = 3          // c == 3  (iota == 2, unused)
    d = 1 << iota  // d == 8  (iota == 3)
)

const (
    u         = iota * 42  // u == 0     (untyped integer constant)
    v float64 = iota * 42  // v == 42.0  (float64 constant)
    w         = iota * 42  // w == 84    (untyped integer constant)
)

const x = iota  // x == 0
const y = iota  // y == 0
const (
    class1 = 0
    class2 // class2 = 0
    class3 = iota  //iota is 2, so class3 = 2
    class4 // class4 = 3
    class5 = "abc" 
    class6 // class6 = "abc"
    class7 = iota // class7 is 6
)

Bug

2022年3月15日,Go官方團隊正式釋出了Go 1.18版本。Go 1.18是Go語言誕生以來變化最大的版本,引入了泛型、Fuzzing、工作區模式等眾多新功能和效能優化。

天下沒有無bug的系統,Go當然也不例外。Go 1.18引入了一個和iota相關的bug。

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

package main

import "fmt"

const C1 = iota
const C2 = iota

func test1() {
    fmt.Println("C1=", C1, " C2=", C2)
}

func main() {
    test1()
}

先思考幾秒鐘。。。


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

C1= 0  C2= 0

在Go 1.18版本,上述程式列印的結果是

C1= 0  C2= 1

很顯然,這是一個bug,因為const C1 = iotaconst C2 = iota是互相獨立的const語句塊,因此這2個const宣告裡的iota的值都是0。

Go官方也認領了這個bug,Go語言的主要設計者Robert Griesemer解釋了這個bug產生的原因:

No need to bisect. This is due to a completely new type checker, so it won't be useful to pin-point to a single change. I've identified the bug and will have a fix in a little bit.

This is clearly a bad bug; but only manifests itself when using iota outside a grouped constant declaration, twice.

As a temporary work-around, you can change your code to:

// OpOr is a logical or (precedence 0)
const (OpOr Op = 0 + iota<<8)

// OpAnd is a logical and (precedence 1)
const (OpAnd Op = 1 + iota<<8)

(put parentheses around the const declarations).

產生這個bug是由於Go引入了全新的型別檢查器導致的。

這個bug只有在全域性未分組的常量宣告才會出現,該bug預計會在Go 1.19版本進行修復。

我們用括號()將宣告包起來,也就是使用分組的常量宣告,就不會有這個bug了。

package main

import "fmt"

const (
    C1 = iota
)
const (
    C2 = iota
)

func test1() {
    fmt.Println("C1=", C1, " C2=", C2)
}

func main() {
    test1()
}

上面程式的執行結果是:

C1= 0  C2= 0

而且,對於區域性常量宣告也是不會有這個bug。

package main

import "fmt"

func test1() {
    const C1 = iota
    const C2 = iota
    fmt.Println("C1=", C1, " C2=", C2)
}

func main() {
    test1()
}

上面程式的執行結果是:

C1= 0  C2= 0

推薦閱讀

開源地址

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

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

個人網站:Jincheng’s Blog

知乎:無忌

References

相關文章