Go闭包【2】 1.22 对 for 循环里闭包陷阱的那个“史诗级更新”

这个更新非常重要,因为它直接终结了 Go 语言过去十年里最常见的 Bug 之一。

在 Go 1.22 之前,我们常说"不要在闭包里直接引用循环变量",但现在,这个规则变了。

1. 过去的"坑"(Go 1.21 及更早)

在旧版本里,for 循环中的变量 i 是地址复用的。每一轮循环,i 都是同一个变量,只是值在变。

复制代码
func main() {
    done := make(chan bool)
    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v) // 闭包引用了同一个变量 v 的地址
            done <- true
        }()
    }
    // ... 等待输出
}
  • 旧版本结果:大概率输出 c, c, c。因为协程启动慢,等它们运行时,变量 v 已经变成最后那个 "c" 了。
  • 当时的解法:必须通过传参或者在循环内定义同名局部变量 v := v

2. Go 1.22 的"史诗级"变化

从 Go 1.22 开始,官方修改了语义:在 for 循环中,每一轮迭代都会创建一个新的变量实例。

  • 现在的代码(就是上面那段):直接输出 a, b, c(顺序随机)。
  • 底层原理:编译器在每一轮循环里都为你自动做了一次变量隔离。每一轮的 v 都是独立的,闭包捕获的是那个瞬间的"专属"变量。

3. 为什么说这比较重要?

  1. 心智负担降低:你再也不用为了防范"闭包陷阱"到处写 v := v 这种看起来像废话的代码了。
  2. 安全性提升:这是 Go 团队为了解决新手甚至老手都常犯的错误,罕见地打破了"保持旧逻辑不变"的传统。
  3. 性能几乎无损:编译器优化得非常好,这种变量分配对绝大多数程序来说没有明显的性能开销。

⚠️ 一个小提醒

这个特性生效的前提是:你的 go.mod 文件里的版本号必须声明为 go 1.22 或更高。如果写的是 1.21,编译器还是会按旧的那套"有坑"的逻辑来执行。

Go 复制代码
package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    values := []string{"a", "b", "c"}
    var wg sync.WaitGroup

    fmt.Println("--- 开始执行 ---")

    for _, v := range values {
        wg.Add(1)
        go func() {
            defer wg.Done()
            // 在 Go 1.22 之前,这里的 v 共享同一个地址,最后打印的通常都是最后一个元素
            // 在 Go 1.22 之后,这里的 v 每轮都是新变量,能正确打印 a, b, c
            fmt.Println("当前值:", v)
        }()
    }

    wg.Wait()
    fmt.Println("--- 执行结束 ---")
}

旧版的标准写法

Go 复制代码
for _, v := range values {
    wg.Add(1)
    go func(val string) {
        defer wg.Done()
        fmt.Println(val)
    }(v) 
}
相关推荐
小满zs12 小时前
Go语言第十二章(通道)
后端·go
a1879272183113 小时前
【算法】链表(二):链表上的双指针——变速、异链与定距,和一份路程账本
数据结构·算法·leetcode·链表·go·指针·环形链表
江湖十年21 小时前
Go 虚荣域名详解:我用 go get -x 抓了个包,终于看清了 Go 工具链“对暗号”的全过程
后端·面试·go
平头哥AI1 天前
Day 22 _ 包装错误别丢链_%w、errors.Is 与 errors.As
android·服务器·学习·golang·go
王码码20352 天前
Go语言CGO:Go与C交互
后端·golang·go·接口
妙码生花2 天前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-下
前端·后端·go
平头哥AI2 天前
Day 21 _ error 是个普通值_errors.New 与 fmt.Errorf 造出来,沿调用栈抛到 main 接住
后端·学习·golang·go
HappRobot2 天前
# Go语法基础
go
Go_error3 天前
在 Go 服务层中如何优雅地管理事务?
后端·go
超级无敌霹雳大狗熊3 天前
以最小demo讲解Eino中的graph编排
go·agent