乐趣区

关于后端:Go-for-循环有时候真的很坑

大家好,我是煎鱼。

不晓得有多少 Go 的面试题和泄露,都和 for 循环无关。明天我在周末认真一看,发现了 redefining for loop variable semantics。

驰名的硬核大佬 Russ Cox 示意他始终在钻研这个问题,并示意十年的教训表明了以后语义的代价是很大的。

问题

案例一:取地址符

在 Go 语言中,咱们写 for 语句时有时会呈现运行和猜测的后果不统一。例如以下第一个案例的代码:

var all []*Item
for _, item := range items {all = append(all, &item)
}

这段代码有问题吗?变量 all 内的 item 变量,存储进去的是什么?是每次循环的 item 值吗?

实际上在 for 循环时,每次存入变量 all 的都是雷同的 item,也就是最初一个循环的 item 值。

这是 Go 面试里经常出现的题目,联合 goroutine 更风骚,毕竟还会存在乱序输入等问题。

如果你想解决这个问题,就须要把程序改写成如下:

var all []*Item
for _, item := range items {
    item := item
    all = append(all, &item)
}

要从新申明一个 item 变量把 for 循环的 item 变量给存储下来再追加进去。

案例二:闭包函数

接下来是第二个案例的代码:

var prints []func()
for _, v := range []int{1, 2, 3} {prints = append(prints, func() {fmt.Println(v) })
}
for _, print := range prints {print()
}

这段程序的输入后果是什么?没有 & 取地址符,是输入 1,2,3 吗?

输入后果是 3,3,3。这又是为什么?

问题的重点之一,关注到闭包函数,实际上所有闭包都打印的是雷同的 v。输入 3,是因为在 for 循环完结后,最初 v 的值被设置为了 3,仅此而已。

如果想要达到预期的成果,仍然是应用万能的再赋值。改写后的代码如下:

for _, v := range []int{1, 2, 3} {
        v := v
        prints = append(prints, func() {fmt.Println(v) })
    }

减少 v := v 语句,程序输入后果为 1,2,3。
认真翻翻你写过的 Go 工程,是不是都很相熟?就这革新办法,赢了。

尤其是配合上 Goroutine 的写法,很多同学会更容易在此翻车。

解决方案

修复思路

实际上 Go 外围团队在外部和社区曾经探讨过许久,心愿从新定义 for 循环的语法。要达到的目标是:使循环变量每次迭代而不是每次循环

解决的方法是:在每个迭代变量 x 的 每个循环体结尾,加一个隐式的再赋值,也就是 x := x,就可能解决上述程序中所隐含的坑。和咱们当初做的一样,只不过咱们是本人手动加的,Go 团队做的是心愿在编译器内隐式解决。

让用户本人决定

比拟难堪的是 Go 团队在 Proposal: Go 2 transition 中禁止从新定义语言,所以 rsc 不能间接这么干。

因而将会由用户本人决定管制这个“毁坏”,形式将会是依据每个包的 go.mod 文件中的 go 行更改语义。

如果咱们是在 Go1.30 对本文探讨的 for 循环改为迭代,那么在 go.mod 文件中的 go 版本申明是将是一个要害。

如下图示:

Go 1.30 或更高版本将会每次迭代变量,而晚期 Go 版本的将每次循环变量。

如此一来上述提到的 for 循环问题都会在肯定范畴内被解决。

总结

for 循环时的变量问题,始终是各大 Go 考官爱考的题目,另外也的确在理论编程 Go 代码时会遇到这类坑。

尽管 rsc 心愿在 go.mod 文件上创始先河,利用 go 版本的申明,去批改语义(不容许增加和删除)。这无疑是给 Go1 兼容性保障开了一个后门。

如果施行,本次变更会导致 Go 的前后版本语义有所不同。还不如变成一个 go.mod 文件的一个语义开关。

这显然是一个很折腾的思考题。

文章继续更新,能够微信搜【脑子进煎鱼了】浏览,本文 GitHub github.com/eddycjy/blog 已收录,学习 Go 语言能够看 Go 学习地图和路线,欢送 Star 催更。

举荐浏览

  • Go 只会 if err != nil?这是不对的,分享这些优雅的解决姿态给你!
  • Go 错误处理新思路?用左侧函数和表达式
  • 先睹为快,Go2 Error 的挣扎之路

Go 图书系列

  • Go 语言入门系列:初探 Go 我的项目实战
  • Go 语言编程之旅:深刻用 Go 做我的项目
  • Go 语言设计哲学:理解 Go 的为什么和设计思考
  • Go 语言进阶之旅:进一步深刻 Go 源码
退出移动版