0

Perhaps it's a very noob question, but I can't exactly understand why this difference happens in my copy methods.

Until now I have had the assumption that you can not copy maps due to the internal structure being a "reference" and all you are passing around is basically a map header. (... paraphrasing a bit)

Given the following simplified example:


// go version go1.19.6 linux/amd64

var globalMap = map[int]string{
    0: "foo",
    1: "bar",
}

func copyGlobalMap() map[int]string {
    copiedMap := make(map[int]string)
    globalMap, copiedMap = copiedMap, globalMap

    return copiedMap
}

func copyMap(sourceMap map[int]string) map[int]string {
    copiedMap := make(map[int]string)
    sourceMap, copiedMap = copiedMap, sourceMap

    return copiedMap
}

func main() {
    fmt.Println("--- Example 1 ----")

    copiedMap := copyMap(globalMap)
    globalMap[1] = "moo"

    fmt.Printf("Original map %+v\n", globalMap)
    fmt.Printf("Copied map %+v\n", copiedMap)

    fmt.Println("--- Example 2 ----")

    copiedMap = copyGlobalMap()
    globalMap[1] = "moo"

    fmt.Printf("Original map %+v\n", globalMap)
    fmt.Printf("Copied map %+v\n", copiedMap)
}

I receive a following output:

--- Example 1 ----
Original map map[0:foo 1:moo]
Copied map map[0:foo 1:moo]
--- Example 2 ----
Original map map[1:moo]
Copied map map[0:foo 1:moo]

As you can see the Example 1 behaves as is always thought - maps are "entangled". But the second example seems to imply that a separate copy was achieved.

Can anyone explain why this (seemingly?) works?


Note: Observed such global swap in a code-base taking a snapshot of a global cache. Firstly I didn't believe it was working properly, but seems to be.

Paperclip
  • 615
  • 5
  • 14
  • 4
    `copiedMap := make(map[int]string)` makes an entirely new map. You're not copying anything, just assigning a new value. The name `copyMap` is probably what makes this confusing. – JimB Apr 12 '23 at 18:14
  • 3
    `copyGlobalMap()` assigns a new, empty map to `globalMap` variable, and returns the "old" map (that was stored in `globalMap` before the assignment). That explains `Example 2` output. – icza Apr 12 '23 at 18:18
  • 2
    Assigning a map to another variable does not copy the elements, it just copies the map header (which actually is a pointer). See [why slice values can sometimes go stale but never map values?](https://stackoverflow.com/questions/55520624/why-slice-values-can-sometimes-go-stale-but-never-map-values/55521138#55521138) and [slice vs map to be used in parameter](https://stackoverflow.com/questions/47590444/slice-vs-map-to-be-used-in-parameter/47590531#47590531) – icza Apr 12 '23 at 18:21
  • 5
    Also note that each parameter is a local variable, assigning anything to it will be discarded when the function returns. Assigning anything to `sourceMap` inside `copyMap()` will be discarded. To modify the "original" value, you must pass a pointer (its address) and modify the pointed value. – icza Apr 12 '23 at 18:22
  • 1
    @icza regarding the "Assigning anything ... will be discarded" you seem to be correct. I seem to remember that you could modify the contents of the map without passing it as pointer, but now testing it does not compute. Which I think makes this "If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller. " claim false I guess. (https://go.dev/doc/effective_go#maps) But in general your explanation sheds some light. I must have been looking at it too long to not realize that I have "write-access" to the global var. – Paperclip Apr 12 '23 at 21:20
  • 1
    And I think you could formulate some answer from it. The Current answer by Nicholas just shows me my own example and IMHO offers nothing . In general I'm aware that you need to do do a "deep copy" of map. Just found my situation confusing. But now realizing that the swap worked on global, because I was able actually retain the swapped state. – Paperclip Apr 12 '23 at 21:24

1 Answers1

-1

This function doesn't copy anything.

func copyMap(sourceMap map[int]string) map[int]string {
    copiedMap := make(map[int]string)
    sourceMap, copiedMap = copiedMap, sourceMap

    return copiedMap
}
source code description
copiedMap := make(map[int]string) Create a new, empty map
sourceMap, copiedMap = copiedMap, sourceMap Swap (exchange) the values of the two variables. copiedMap now locates the original sourceMap; sourceMap now locates the new, empty map that you created.
return copiedMap returns the current value of copiedMap to the caller. That would be the original sourceMap

If you do this:

src := map[int]string{ 1:"one", 2:"two", 3:"three" }
cpy := copyMap(src)
src[4] = "quatre"

you'll find that both src and cpy now have 4 elements.

If you actually want to create a copy of a map, it's just:

func copyMap(src map[int]string) map[int]string {
    cpy := make(map[int]string)
    for k, v := range src {
        cpy[k] = v
    }
    return cpy
}
Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
  • 2
    I understand the creation of new map and replacing a value. The question I'm interested is that why the swap on the global map does not get updated after I update the value on the swapped map. Why are Example 1 and Example 2 not resulting in exactly the same result? – Paperclip Apr 12 '23 at 21:03
  • 2
    Got my answer from the comments already. The reason is that the swap in the copyMap is just not effective after the method returns. In case of the global copy the swap is actually effective - global value changed and old value returned as result. – Paperclip Apr 12 '23 at 21:27