Home > front end >  passing map between packages in golang
passing map between packages in golang

Time:07-24

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.

CodePudding user response:

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)
}

CodePudding user response:

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"
}
  •  Tags:  
  • go
  • Related