passing map between packages in golang

huangapple go评论85阅读模式
英文:

passing map between packages in golang

问题

在Go语言中,map是以引用的形式在函数之间传递的,但是我今天遇到了一个奇怪的情况。代码的运行结果并不是我所想象的那样。我将其简化为以下几行代码。

.
├── go.mod
├── main.go
├── packageA
     └── a.go
└── packageB
    └── b.go

main.go文件

package main

import (
	"gostudy/packageA"
	"gostudy/packageB"
)

func main() {
	packageB.UseMap(packageA.M, packageA.InitMap)
}

a.go文件

package packageA

var M map[string]string

func InitMap() {
	M = make(map[string]string)
	M["hello"] = "go"
}

b.go文件

package packageB

import "fmt"

func UseMap(m map[string]string, callback func()) {
	callback()
	fmt.Println(m)
}

如你所见,在a.go文件中只声明了一个全局变量。我原以为上述程序应该输出map[hello:go],但实际上输出的是一个空的map[]。我对此感到非常困惑,希望能得到答案。

英文:

In golang, I know that map is passed between functions in the form of reference, but I encountered a strange situation today. The running result of the code is not what I imagined. I simplified it into the following lines of code.

.
├── go.mod
├── main.go
├── packageA
│   └── a.go
└── packageB
    └── b.go

main.go file

package main

import (
	"gostudy/packageA"
	"gostudy/packageB"
)

func main() {
	packageB.UseMap(packageA.M, packageA.InitMap)
}

a.go

package packageA

var M map[string]string

func InitMap() {
	M = make(map[string]string)
	M["hello"] = "go"
}

b.go

package packageB

import "fmt"

func UseMap(m map[string]string, callback func()) {
	callback()
	fmt.Println(m)
}

As you can see, there is only one variable globally declared in the a.go file. I thought the above program should output map[hello:go], but it actually outputs an empty map[]. I'm very confused about this and hope to get an answer.

答案1

得分: 3

你正在将旧的地图值作为参数传递,然后调用函数来用新版本的地图替换它。

假设packageA.M包含值map[string]string{"foo": "bar"}main()函数读取该变量并获取对该地图的引用,然后将其和函数一起传递给packageB.UseMap()

packageB.UseMap()内部,你的代码通过回调调用packageA.InitMap()。这不会修改现有的地图;相反,它创建一个新的地图,将其分配给全局变量,并填充它。拥有旧地图副本的任何内容都不受影响,并且你展示的代码不会重新读取packageA.M的值。

我建议完全放弃全局变量:它可能会使代码难以测试,并且一旦开始使用goroutine,就可能出现潜在问题。只需让设置函数返回新的地图。

package packageA

func InitMap() map[string]string {
        return map[string]string{"hello": "go"}
}
package packageB

func UseMap(callback func() map[string]string) {
        m := callback()
        fmt.Println(m)
}
package main

import "packageA"
import "packageB"

func main() {
        packageB.UseMap(packageA.InitMap)
}
英文:

You're passing the old value of the map as a parameter, before you invoke the function to replace it with a new version of the map.

Let's say packageA.M contains the value map[string]string{"foo": "bar"}. The main() function reads the variable and gets a reference to this map, and passes it and the function to packageB.UseMap().

Inside packageB.UseMap(), your code calls packageA.InitMap() via the callback. This does not modify the existing map; instead, it creates a new map, assigns it to the global variable, and populates it. Anything that had a copy of the old map is unaffected, and the code you show doesn't re-read the value of packageA.M.

I'd recommend dispensing with the global variable entirely: it can make the code hard to test and there are potential problems once you start using goroutines. Just have your setup function return the new map.

package packageA

func InitMap() map[string]string {
        return map[string]string{"hello": "go"}
}
package packageB

func UseMap(callback func() map[string]string) {
        m := callback()
        fmt.Println(m)
}
package main

import "packageA"
import "packageB"

func main() {
        packageB.UseMap(packageA.InitMap)
}

答案2

得分: 1

只是作为对接受的答案的一个附注,如果你看一下这个:

// ...
import (
    "reflect"
    "fmt"
)

// ... 其他函数
// 我在一个单独的包中定义了所有的函数,所以我可以在这里访问它们
func UseMap(m map[string]string, callback func()) {
    fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // 打印 true,它们具有相同的引用
    callback() 
    // 在回调函数内部,全局变量 M 会获得一个全新的引用
    // 当 "M = make(...)" 时
    // 然后 =>
    fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // 打印 false
}

如果你想在不改变你的 API 的情况下避免这种情况,你可以在包 A 中这样做:

package packageA

var M map[string]string = make(map[string]string)

func InitMap() {
    M["hello"] = "go"
}
英文:

Just as a side note to the accepted anwer, if you take a look at this:


// ...
import (
    "reflect"
    "fmt"
)

// ... other functions
// I defined all of the functions in a single paackage, so I can access them both here
func UseMap(m map[string]string, callback func()) {
    fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // prints true, they have the same reference
    callback() 
    // inside callback, M global variable takes a whole new reference
    // when "M = make(...)"
    // and then => 
    fmt.Println(reflect.ValueOf(m).Pointer() == reflect.ValueOf(M).Pointer()) // prints false
}

If you want to avoid this without changing your apis, you can do this in your package A:

package packageA

var M map[string]string = make(map[string]string)

func InitMap() {
    M["hello"] = "go"
}

huangapple
  • 本文由 发表于 2022年7月24日 17:54:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/73097353.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定