英文:
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"
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论