乐趣区

关于golang:注意Go-118版本iota的bug

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

举荐浏览

  • 泛型

    • 泛型:Go 泛型入门官网教程
    • 泛型:一文读懂 Go 泛型设计和应用场景
    • 泛型:Go 1.18 正式版本将从规范库中移除 constraints 包
    • 泛型:什么场景应该应用泛型
  • Fuzzing

    • Fuzzing: Go Fuzzing 入门官网教程
    • Fuzzing: 一文读懂 Go Fuzzing 应用和原理
  • 工作区模式

    • Go 1.18:工作区模式 workspace mode 简介
    • Go 1.18:工作区模式最佳实际

开源地址

文章和示例代码开源在 GitHub: Go 语言高级、中级和高级教程。

公众号:coding 进阶。关注公众号能够获取最新 Go 面试题和技术栈。

集体网站:Jincheng’s Blog。

知乎:无忌

References

  • https://twitter.com/go100and1…
  • https://github.com/golang/go/…
  • https://go.dev/ref/spec#Iota
退出移动版