让 iota 从 a +1 开始增量《这是一种误导,是一种错误逻辑》

我在网上看到了一篇《十条有用的 GO 技术》其实是很多篇,该死的爬虫。

下面的内容都是误导别人的错误逻辑,可能是原创作者当时入坑不深,我发表此文章的本意仅仅是为了讨论技术,可能会使某人不开心,但一个正确的技术答案,是所有人想要的。

其中第六条这样解释:

6. 让 iota 从 a +1 开始增量

在前面的例子中同时也产生了一个我已经遇到过许多次的 bug。假设你有一个新的结构体,有一个 State 字段:

type T struct {
Name  string
Port  int
State State
}

现在如果基于 T 创建一个新的变量,然后输出,你会得到奇怪的结果 (http://play.golang.org/p/LPG2RF3y39) :

func main() {
t := T{Name: "example", Port: 6666}
// prints: "t {Name:example Port:6666 State:Running}"
fmt.Printf("t %+vn", t)
}

看到 bug 了吗?State 字段没有被初始化,Go 默认使用对应类型的零值进行填充。由于 State 是一个整数,零值也就是 0,但在我们的例子中它表示 Running。
如何知道 State 被初始化了?如何得知是在 Running 模式?事实上,没有很好的办法区分它们,并且它们还会产生更多未知的、不可预测的 Bug。不过,修复这个很容易,只要让 iota 从 +1 开始 (http://play.golang.org/p/VyAq-3OItv):

const (
Running State = iota + 1
Stopped
Rebooting
Terminated
)

现在 t 变量将默认输出 Unknown,是不是?

func main() {
t := T{Name: "example", Port: 6666}
// 输出: "t {Name:example Port:6666 State:Unknown}"
fmt.Printf("t %+vn", t)
}

不过让 iota 从零值开始也是一种解决办法。例如,你可以引入一个新的状态叫做 Unknown,将其修改为:

const (
Unknown State = iota 
Running
Stopped
Rebooting
Terminated
)

OK,上面的内容都是误导别人的错误逻辑,可能是原创作者当时入坑不深,我发表此文章的本意仅仅是为了讨论技术,可能会使某人不开心,但一个正确的技术答案,是所有人想要的。

开始正式讨论

先放一段上面的代码

type T struct {
    Name  string
    Port  int
    State State
}

type State int

const (
    Running State = iota //iota将const常量依次绑定了数值0,1,2,3
    Stopped
    Rebooting
    Terminated
)
//我们这里重写了string官方函数,
//使得State类型的打印结果不是01234... 而是指定的字符串。
func (s State) String() string {
    switch s {
    case Running:
        return "Running"
    case Stopped:
        return "Stopped"
    case Rebooting:
        return "Rebooting"
    case Terminated:
        return "Terminated"
    default:
        return "Unknown"
    }
}
func main() {
    t := T{Name: "example", Port: 6666}
    fmt.Printf("t %+v\n", t)
}

//t {Name:example Port:6666 State:Running}

我们看一下有疑问的地方 main 里的 fmt.Printf("t %+v\n", t)

t 结构体的第三个字段没有赋值,会默认赋予零值。
但是为什么T.State打印出来是Running,这是正确的,请看我的解释:

❌有人会这样想:结果是出乎意料的,T.State的打印结果应该是0。因为State 是一个整数,零值也就是 0,不应该是Running。。。。这样的逻辑思想是错误的。

2019年11月08日11:37:22新增解释:\
温故而知新:代码中State实现了string接口,而T又包含了State,因此T也实现了string接口,所以main程序里T会调用重写后的string函数。

解决:

1️⃣可以使用标准库的string打印:fmt.Printf("t %#v\n", t)

2️⃣或者我们去掉自定义的string方法即可。

3️⃣或者修改string方法的接收器

4️⃣或者修改T.State类型

✅总之,在string判断中,只要实现了string接口,则一定会调用重构的string。

✅stackoverflow论坛的回答:(我提问这个问题之后,被很多人鄙视我。。。)

T.State的类型为State。这与字段T无关。State不是T(struct)的一部分,仅仅是成员。
T.State类型的零值为0,也等于Running。
因此,当您分配给T的实例且T.State未初始化时,T.State会得到零值,即
Running

✅github,GO官方人员回答:(有幸得到了官方的回复)

就像@DisposaBoy所说的,这是因为State的任何整数值在打印时都会被字符串化%+v,在这种情况下,设置了State的零值,所以Running将其打印出来。参见https://yourbasic.org/golang/iota/

本作品采用《CC 协议》,转载必须注明作者和本文链接
最初的时候也是最苦的时候,最苦的时候也是最酷的时候。《转自SmauelL》
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!