【go】忽略range循环中使用指针的影响?

经典面试题:

func main() {
	s := Store{
		m: make(map[string]*Customer),
	}
	s.storeCustomers4([]Customer{
		{ID: "1", Balance: 10},
		{ID: "2", Balance: -10},
		{ID: "3", Balance: 0},
	})
	print(s.m)
}

func (s *Store) storeCustomers(customers []Customer) {
	for _, customer := range customers {
		fmt.Printf("%p\n", &customer)
		s.m[customer.ID] = &customer
	}
}

说最后输出的都是id:3 。。。
but,这一切都在1.23 改变了,这个有点反人类的指针指向最后一个,被1.23 解决了

Go 1.23中关于Range循环的变化

Go 1.23版本对range循环引入了一个重要的行为变更,这是Go 1.x语义演进的一个罕见例子。以下是这个变化的详细说明:

主要变化:迭代变量的地址不再复用

在Go 1.23之前,range循环的迭代变量在每次迭代中都使用相同的内存地址,只是值会改变。这导致了我们讨论的问题——当在循环中获取迭代变量的地址时,所有迭代都返回相同的地址。

Go 1.23的变化

  • 每次迭代现在会为迭代变量分配新的内存地址
  • 这意味着获取迭代变量的地址(如&customer)在每次迭代中会返回不同的地址

变更的动机

这个变更的主要动机是解决一个常见的错误来源和困惑点:

  1. 这个问题已经成为Go开发者的常见痛点,尤其是在使用指针、闭包和goroutine时
  2. 许多开发者对此行为感到意外,因为它与直觉相悖
  3. 它导致了难以调试的错误,特别是在并发程序中

变更的官方说明

这个变更是在提案#56739中提出并实现的,标题为"cmd/compile: for-range loop iteration variables should not be reused"。

提案的核心论点是:

  • range变量复用导致了许多意外错误
  • 新的行为更符合开发者的直觉和期望
  • 这种修改很少会破坏依赖旧行为的正确代码

技术实现

在内部实现上,Go编译器现在会:

  1. 在循环的每次迭代中重新分配迭代变量
  2. 这适用于所有形式的range循环(切片、数组、映射、通道等)
  3. 对于for i, v := range x形式,iv都会在每次迭代中获得新的地址

对现有代码的影响

这个变更的影响主要是积极的:

  1. 修复了常见错误

    for _, v := range items {
        pointers = append(pointers, &v) // 现在正常工作!
    }
    
  2. 对goroutine更安全

    for _, v := range items {
        go func() {
            fmt.Println(v) // 现在捕获的是当前迭代的值
        }()
    }
    
  3. 闭包捕获更符合直觉

    funcs := make([]func(), 0, len(items))
    for _, v := range items {
        funcs = append(funcs, func() {
            fmt.Println(v) // 现在每个函数打印不同的值
        })
    }
    

向后兼容性考虑

这是一个罕见的例外,Go团队决定在Go 1.x版本中改变语言行为。通常,Go的兼容性承诺禁止这类变更。

如果您的代码需要与较旧的Go版本兼容,您仍然应该:

  1. 在循环中创建局部变量
  2. 使用函数参数捕获当前值
  3. 使用索引形式的range循环

总结

Go 1.23中的这个变化是对语言的重要改进:

  1. 它解决了一个常见的错误来源
  2. 使代码行为更符合开发者的直觉
  3. 简化了与指针、闭包和goroutine相关的代码

这个变更展示了Go团队在语言演进中的谨慎平衡——在大多数情况下保持严格兼容性,但在必要时也愿意进行精心考虑的改进,即使这意味着改变现有行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值