admin管理员组

文章数量:1392081

I am pretty new to Go. My expectation when initializing a variable that was passed by reference is that this variable will now be initialized everywhere it is in scope, but that does not seem to be the case. Can anyone explain why this is? Below is some example code:

package main

import "fmt"

func updateMap(m map[byte]bool) {
    if m == nil {
        m = make(map[byte]bool)
    }

    m['c'] = true
}

func main() {
    myMaps := make([]map[byte]bool, 10)

    for _, m := range myMaps {
        updateMap(m)
        fmt.Println(m)
    }
}

The maps all print as empty, however if I move the initialization to the for loop, just before calling updateMap, then the maps print with one element each, as expected.

I am pretty new to Go. My expectation when initializing a variable that was passed by reference is that this variable will now be initialized everywhere it is in scope, but that does not seem to be the case. Can anyone explain why this is? Below is some example code:

package main

import "fmt"

func updateMap(m map[byte]bool) {
    if m == nil {
        m = make(map[byte]bool)
    }

    m['c'] = true
}

func main() {
    myMaps := make([]map[byte]bool, 10)

    for _, m := range myMaps {
        updateMap(m)
        fmt.Println(m)
    }
}

The maps all print as empty, however if I move the initialization to the for loop, just before calling updateMap, then the maps print with one element each, as expected.

Share Improve this question asked Mar 11 at 16:54 glarikglarik 11 bronze badge 2
  • 3 The same advice from similar questions about a lot of other popular languages (JavaScript, Python, C, Java, many more) applies – there’s no such thing as “pass by reference” in Go. The values of some types (e.g. pointers, slices, and maps) are themselves references (these types are sometimes called “reference types”). – Ry- Commented Mar 11 at 17:00
  • Maybe it's clearer for you if you look at it as a pointer: go.dev/play/p/5ndFUkjWUOs – Mr_Pink Commented Mar 11 at 17:12
Add a comment  | 

2 Answers 2

Reset to default -1

You are not passing a "reference", as m holds a value.

This is possible of you pass a map pointer to updateMap.

Caveat

This will not work, because m is a copy:

for _, m := range myMaps

So you need to access the slice by its index directly:

for i := range myMaps

Fixed code

Here is the code for reference:

package main

import "fmt"

func updateMap(m *map[byte]bool) {
    if *m == nil {
        *m = make(map[byte]bool) // Modify
    }
    (*m)['c'] = true
}

func main() {
    myMaps := make([]map[byte]bool, 10)

    for i := range myMaps {
        updateMap(&myMaps[i]) // Reference
        fmt.Println(myMaps[i])
    }
}

Output

map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]
map[99:true]

Remarks

I will note that it it generally better to pass in a value and return a value like Kamran Khalid suggests.

Pure functions can make the code easier to follow. When you mutate values inside a function, there are side-effects that can be undesirable and cause very difficult bugs to happen.

Your main initialises a slice of maps (references), but those map references are themselves nil since they are not initialised.

When updateMap is then called, it receives a nil reference; this should help understand that what has been passed is not a reference to a particular element in the slice, but (a copy of) the reference held in each element. Each reference being is nil.

When updateMap receives the nil reference a new map is initialised and a reference to that new map then overwrites the nil reference that was passed in. So when you set the c key, you are setting it in a map which is only referenced by the m variable in updateMap.

This should then make it clear why initialising the maps in each slice element in the loop works differently.

    for _, m := range myMaps {
        m = make(map[byte]bool)
        updateMap(m)
        fmt.Println(m)
    }

You did not post what your version of "moving initialisation to the loop" looked like, and it should be noted that the above does not modify the myMaps slice of maps. It takes each element from myMaps (still nil in this version at the start of each iteration) and again overwrites that copy of the reference with a reference to a newly initialised map.

The Println is being given that same, new reference and outputs the map as updated by updateMap. But the corresponding element in the slice is still nil, as will be seen if you range over myMaps to inspect it again:

    for _, m := range myMaps {
        m = make(map[byte]bool)
        updateMap(m)
        fmt.Println(m)
    }

    for _, m := range myMaps {
        fmt.Println(m)
    }

The way to fix that is to range over the indexes of the slice and work with those directly, which might look similar to:

for i := range myMaps {
    myMaps[i] = make(map[byte]bool)

    m := myMaps[i]
    updateMap(m)

    fmt.Println(m)
}

You could modify updateMaps to accept a *map[byte]bool (pointer to map) but this would be considered unusual or, in Go parlance, non-idiomatic and therefore not something you should do without a very good reason, which I don't this is.

本文标签: