How to copy a map in golang?

Issue

I can offer decomposition of map to 2 slices example:

func decomposeMap(m map[string]int) ([]string, []int) {
  var i uint
  l := len(m)
  keys, values := make([]string, l), make([]int, l)
  for keys[i], values[i] = range m {
    i++
  }
  return keys, values
}

but I am failing to write map copying:

func copyMap(m map[string]int) map[string]int {
  m2 := make(map[string]int, len(m))
  for id, m2[id] = range m {} // error - id is not declared
  for id, m2[id] := range m {} // error with m2[id] already declared
  // id should not be accessible here, it should exist only inside loop
  return m2
}

I can declare id as a var, but I dont want it to be available outside for loop. How can i mix assigment and declaration, eg: for id:=, m[id]= range m {} ?
So it will declare index just inside for loop, and will be not accessible outside?

Solution

The id variable must be declared before for, because you can’t use short variable declaration with m2[id].

func copyMap(m map[string]int) map[string]int {
    m2 := make(map[string]int, len(m))
    var id string
    for id, m2[id] = range m {
    }
    return m2
}

But! This won’t duplicate the map! The key is only assigned to id after m2[id] is already evaluated, so this loop will assign values to keys of the previous iteration, this is not duplicating, this is "shuffling"!

This is basically a tuple assignment (key and value are assigned to id, m2[id]). Spec: Assignments:

The assignment proceeds in two phases. First, the operands of index expressions and pointer indirections (including implicit pointer indirections in selectors) on the left and the expressions on the right are all evaluated in the usual order. Second, the assignments are carried out in left-to-right order.

So first id and m2[id] are evaluated (including the index expression), so id is not "yet" changed, so the value from the previous iteration is used, and only after this are the new key and values assigned.

To demonstrate, see:

m := map[string]int{
    "one":   1,
    "two":   2,
    "three": 3,
}
m2 := copyMap(m)
fmt.Println(m2)

Output (try it on the Go Playground):

map[:1 one:2 two:3]

The values are assigned to different keys (different than in the source map), and one value is assigned to the empty string key in the first iteration (the default, zero value of id).

To duplicate the map, simply use:

for id, v := range m {
    m2[id] = v
}

Or if you want to avoid the temporary assignment:

for id := range m {
    m2[id] = m[id]
}

Answered By – icza

Answer Checked By – Robin (GoLangFix Admin)

Leave a Reply

Your email address will not be published.