for-range的坑

起因

在写毕设时碰到了这样一个问题:为 Post 数组中每个 Post 的 UUID 值生成一个QRCode,听起来很简单对吧,for-range遍历就好了。生成QRCode的函数如下所示:

func (p *Post) initQRCode() { ... }

这么写的本意是想避免每次调用时产生的一次内存拷贝,所以就用了指针接收者,可是运行时却发现所有的QRCode值都一样,直接当场愣住。

分析

问题出在哪里?看看事发地点的代码:

for _, p := range posts {
    // ...
    go p.initQRCode()
}

在这里我错误地认为每次迭代都会声明并赋值一个新变量 p,然而从始至终只有这一个 p 而已:= 与 range 这种结合的写法让我产生了这样的错觉。所以,对于 for-range 的正确理解是:p 会遍历复制目标数组中的值。当循环结束时,依赖 p 的指针得到的值都会是最后一次循环p被赋予的值。

更重要的一点是,这些 goroutine 很可能不会在循环结束之前就执行。

解决

正确的改法有两种,但都是一个意思:必须在每次迭代时就将值计算出来

所以我们在循环内新声明一个变量,将 p 赋值给它,然后调用那个指针接受值的方法,或者是使用值接受者的方法。

后记

这个问题已被官方列为常见错误之一了