Golang 1.17循环中变量覆盖问题解析

Golang 1.17循环中变量覆盖问题解析 Golang 出现了一些奇怪的行为。我不确定这是 Go 1.17 的问题还是我的代码问题。

基本上,在下面的代码中,我需要保留标题内容为旧值(该值可能与第二个对象的值不同)。我尝试在循环中更新一个对象。更新操作基于第二个对象执行,而该对象在循环中从未被更新。

但是当循环进行第二次迭代时,第二个对象的值却在改变。这不应该发生,因为我没有在任何地方更新第二个对象。

这里不应该更新作为 postedSection 的第二个对象。

package main

import (
	"fmt"
)

type Section struct {
	PageId   int                    `json:"page_id" bson:"page_id"`
	Settings map[string]interface{} `json:"settings" bson:"settings"`
	Status   bool                   `json:"status" bson:"status"`
}

func main() {
	var languages = []string{"en", "fr"}

	postedSection := Section{}
	postedSection.PageId = 432
	postedSection.Settings = map[string]interface{}{"title": map[string]interface{}{"content": "es posted title"}}
	postedSection.Status = true

	langWiseSections := make(map[string]Section)
	enSec := Section{}
	enSec.PageId = 432
	enSec.Settings = map[string]interface{}{"title": map[string]interface{}{"content": "en title"}}
	enSec.Status = true
	langWiseSections["en"] = enSec

	esSec := enSec
	esSec.Settings = map[string]interface{}{"title": map[string]interface{}{"content": "es title"}}
	langWiseSections["es"] = esSec

	frSec := enSec
	frSec.Settings = map[string]interface{}{"title": map[string]interface{}{"content": "fr title"}}
	langWiseSections["fr"] = frSec

	for _, language := range languages {
		fmt.Println("language-----", language)
		section := langWiseSections[language]
		fmt.Println("postedSection", postedSection)
		section = GetOtherLangSecDataToUpdate(section, postedSection)
		fmt.Println("section", section)

	}
}

func GetOtherLangSecDataToUpdate(otherLangSec, updatedSection Section) Section {
	for fieldName, oldSettIntrfc := range otherLangSec.Settings {
		if oldSettIntrfc != nil {
			otherLangSec.Settings[fieldName] = updatedSection.Settings[fieldName]
			/* continue for all types which are not map[string]interface{} */
			switch oldSettIntrfc.(type) {
			case map[string]interface{}: // do nothing if map[string]interface{}
			default:
				continue
			}
			oldSettings := oldSettIntrfc.(map[string]interface{})
			otherLangSecFieldObj := otherLangSec.Settings[fieldName].(map[string]interface{})

			switch fieldName {
			case "title":
				if oldSettings["content"] != nil {
					otherLangSecFieldObj["content"] = oldSettings["content"]
				}
			}
			otherLangSec.Settings[fieldName] = otherLangSecFieldObj
		}
	}
	fmt.Println("updatedSection", updatedSection)
	return otherLangSec
}

更多关于Golang 1.17循环中变量覆盖问题解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

这是 Go 语言的默认行为,还是在 Go 1.17 中更新的?

更多关于Golang 1.17循环中变量覆盖问题解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢你的帮助。如果你提供的解决方案在我们的系统中不适用,我会在这里联系你。

是的,我需要先将更新后的映射复制到旧的映射中,因为可能还有其他字段也需要在所有语言中同步更新,但不包括文本内容。这就是我进行复制的原因。我该如何实现这个需求?

你好,@Amandeep_Kaur

我认为在第49行,你正在复制嵌套的映射:

otherLangSec.Settings[fieldName] = updatedSection.Settings[fieldName]

因此,当你稍后通过 otherLangSecFieldObj 更改它时,会修改通过 updatedSection 传入的同一个映射。

这种行为并非Go 1.17所特有。映射(map)和切片(slice)是“类引用”的值;它们本质上是指针,因此当你复制它们时,你复制的是指针:

source := map[string]int{"a": 1}

dest := source
dest["a"] = 2

fmt.Println(source["a"]) // 将打印 2

这是一个典型的Go语言中map引用共享导致的问题,不是Go 1.17的bug。问题出现在GetOtherLangSecDataToUpdate函数中对updatedSection.Settings的修改上。

问题分析

当你在循环中调用GetOtherLangSecDataToUpdate(section, postedSection)时,虽然postedSection是按值传递的,但其中的Settings字段是一个map[string]interface{},而map在Go中是引用类型。

关键问题代码:

otherLangSec.Settings[fieldName] = updatedSection.Settings[fieldName]

这行代码实际上将updatedSection.Settings["title"]的引用赋值给了otherLangSec.Settings["title"],导致两个map共享同一个底层数据。

示例重现

简化代码来展示问题:

package main

import "fmt"

type Section struct {
    Settings map[string]interface{}
}

func main() {
    posted := Section{
        Settings: map[string]interface{}{
            "title": map[string]interface{}{"content": "posted title"},
        },
    }
    
    other := Section{
        Settings: map[string]interface{}{
            "title": map[string]interface{}{"content": "other title"},
        },
    }
    
    // 修改other的title指向posted的title map
    other.Settings["title"] = posted.Settings["title"]
    
    // 现在修改other的title content
    titleMap := other.Settings["title"].(map[string]interface{})
    titleMap["content"] = "modified content"
    
    // 你会发现posted的title也被修改了!
    fmt.Println("posted title:", posted.Settings["title"]) // 输出: map[content:modified content]
}

解决方案

需要深度复制map,而不是共享引用:

func GetOtherLangSecDataToUpdate(otherLangSec, updatedSection Section) Section {
    for fieldName, oldSettIntrfc := range otherLangSec.Settings {
        if oldSettIntrfc != nil {
            // 深度复制map而不是共享引用
            switch updatedVal := updatedSection.Settings[fieldName].(type) {
            case map[string]interface{}:
                // 创建新的map并复制内容
                copiedMap := make(map[string]interface{})
                for k, v := range updatedVal {
                    copiedMap[k] = v
                }
                otherLangSec.Settings[fieldName] = copiedMap
            default:
                otherLangSec.Settings[fieldName] = updatedSection.Settings[fieldName]
            }
            
            switch oldSettIntrfc.(type) {
            case map[string]interface{}:
                // 处理嵌套逻辑
                oldSettings := oldSettIntrfc.(map[string]interface{})
                otherLangSecFieldObj := otherLangSec.Settings[fieldName].(map[string]interface{})
                
                switch fieldName {
                case "title":
                    if oldContent, exists := oldSettings["content"]; exists {
                        otherLangSecFieldObj["content"] = oldContent
                    }
                }
            default:
                continue
            }
        }
    }
    return otherLangSec
}

或者使用更通用的深度复制函数:

func deepCopyMap(original map[string]interface{}) map[string]interface{} {
    copied := make(map[string]interface{})
    for k, v := range original {
        switch val := v.(type) {
        case map[string]interface{}:
            copied[k] = deepCopyMap(val)
        default:
            copied[k] = v
        }
    }
    return copied
}

// 在函数中使用
otherLangSec.Settings[fieldName] = deepCopyMap(updatedSection.Settings[fieldName].(map[string]interface{}))

这个问题与Go版本无关,而是map引用语义的常见陷阱。在需要独立副本时,必须显式复制map的内容。

回到顶部