98

I have the data structure like this:

type Snapshot struct {
  Key   string
  Users []Users
}

snapshots := make(map[string] Snapshot, 1)

// then did the initialization
snapshots["test"] = Snapshot {
  Key: "testVal",
  Users: make([]Users, 0),
}

Users is another struct.

Then when I tried to append some new Users values in the Users slice like this:

snapshots["test"].Users = append(snapshots["test"].Users, user)

I kept getting this error:

cannot assign to struct field snapshots["test"].Users in map

Also tried the workaround here https://github.com/golang/go/issues/3117 so like this:

tmp := snapshots["test"].Users
tmp = append(tmp, user)
snapshots["test"].Users = tmp

But no luck, still exactly same error.

And also tried to declare the map with pointer, so: snapshots := make(map[string] *Snapshot, 1), still no luck.

Darshan Rivka Whittle
  • 32,989
  • 7
  • 91
  • 109
lnshi
  • 2,310
  • 3
  • 19
  • 42
  • Refer to http://stackoverflow.com/questions/32751537/why-do-i-get-a-cannot-assign-error-when-setting-value-to-a-struct-as-a-value-i – keno Mar 05 '17 at 07:33
  • @keno, thanks for ur response, finally I figured out the reason why after I already use pointer it still cannot work, it is because I did `snapshots := make(map[string] Snapshot, 1)`, then i think the `len(snapshots)` will be 1, later I initialized the map with one for loop, which used the value `len(snapshots)`,.... so that mean the initialization process never get run.... then after i used pointer, i get this error: `panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x78 pc=0x427bb9d]` – lnshi Mar 05 '17 at 08:06

5 Answers5

84

For those looking for a simpler example:

This is wrong:

type myStruct struct{
   Field int
}

func main() {
   myMap := map[string]myStruct{
        "key":{
            Field: 1,
        },
   }

   myMap["key"].Field = 5
}

Because myMap["key"] is not "addressable".

This is correct:

type myStruct struct{
   Field int
}

func main(){
   myMap := map[string]myStruct{
       "key":{
           Field: 1,
       },
   }

   // First we get a "copy" of the entry
   if entry, ok := myMap["key"]; ok {

       // Then we modify the copy
       entry.Field = 5
    
       // Then we reassign map entry
       myMap["key"] = entry
   }

   // Now "key".Field is 5
   fmt.Println(myMap) // Prints map[key:{5}]
}

Here you have a working example.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
Jairo Lozano
  • 3,883
  • 3
  • 20
  • 29
  • 11
    For anyone else who wants to understand this more, [this](https://utcc.utoronto.ca/~cks/space/blog/programming/GoAddressableValues) seems to be a discussion of Addressability - though it seems to mostly focus on pointers, and doesn't explain _why_ this choice was made in the Go Language (i.e. why the language doesn't compile the source code "assign to a field-of-a-struct-in-an-array" to "create a temporary variable of the struct-in-an-array, then assign to a field of it"). If an experienced Gopher can explain the justification for this choice, I'd love to understand it! – scubbo May 11 '22 at 17:43
  • Would also love to hear an explanation. This really seems as counter intuitive to me. – naneri Jul 28 '22 at 17:50
  • 1
    Every `if` may have an `else` for which `entry` would be the zero value: [Golang syntax in "if" statement with a map](https://stackoverflow.com/q/52705926/86967) – Brent Bradburn Apr 15 '23 at 19:55
16

For my use case, I needed to update the entries fairly often. Thus, modifying the copy and reassigning it to the map entry would have been very inefficient. An alternative way could be to use a pointer to a struct instead. (I know it won't fit for all use cases, but just in case yours is flexible enough to use either a struct or a pointer to it...)

type bigStruct struct {
    val1 int
    val2 bool
    val3 string
}

newMap := make(map[string]*bigStruct)

newMap["struct1"] = &bigStruct{1, true, "example1"}

// and can now modify the entries normally
newMap["struct1"].val1 = 2
newMap["struct1"].val2 = false
newMap["struct1"].val3 = "example2"

See the full code here.

Ankit Kumar
  • 1,145
  • 9
  • 30
  • It is not easy to reason about performance with pointers and struct. Call by copy for functions is in many cases faster in go instead of using pointers. Pointers may lead to less optimized machine-code, more pressure on the garbage collector and less locality... so if performance is relevant always benchmark both options in a realisitc use-case. – Falco Apr 28 '23 at 11:22
14

First, for this question, the solution in this post Why do I get a "cannot assign" error when setting value to a struct as a value in a map? works perfectly fine.

Then, finally figured out why after I already changed to use pointer my case still doesn't work, refer to the below very simple code:

a := make([]int, 3)
fmt.Println(len(a))

b := make(map[string]string, 3)
fmt.Println(len(b))

What do think the output will be? I simply thought it is all would be: 3, but actually for the map, the output will be 0

Then later in the map initialization process, i used a for loop and with this value len(snapshots), that means the initialization process will never get run...

Yea, that is the reason.

Vitaly Zdanevich
  • 13,032
  • 8
  • 47
  • 81
lnshi
  • 2,310
  • 3
  • 19
  • 42
2

What I ended up doing to use my struct map in a loop was the following:

type testStruct struct {
  a string
  b int
}

func main() {
  mapTest := make(map[string]testStruct)
  abc := [3]string{"a", "b", "c"}

  for i := 0; i < len(abc); i++ {
    var temp testStruct
    temp.a = abc[i]
    temp.b = i
    mapTest[abc[i]] = temp
  }

  fmt.Println(mapTest)
}

Output should be:

map[b:{b 1} c:{c 2} a:{a 0}]

It's not appending, but should work to assign multiple values to a struct map, alternatively you could do the following and allow the map to reference its own values:

func main() {
  mapTest := make(map[string]testStruct)

  abc := [3]string{"a", "b", "c"}
  for i := 0; i < len(abc)*2; i++ {
    temp := mapTest[abc[i%3]]
    temp.a = abc[i%3]
    temp.b = temp.b + i
    mapTest[abc[i%3]] = temp
  }

  fmt.Println(mapTest)
}

Which should output:

map[a:{a 3} b:{b 5} c:{c 7}]

Note that no errors are raised when we reference an empty struct value, this is because when we initialize our struct, its values start out as empty values but not nil (0 for int, "" for string, etc.)

Devyzr
  • 299
  • 5
  • 13
1

The reason that it is not possible to do what the Asker is trying is due to addressability.

The exact error you are receiving as per the errors in the spec is _UnaddressableFieldAssign which is raised when you try to assign to an unaddressable value.

_UnaddressableFieldAssign occurs when trying to assign to a struct field in a map value. Example:

   func f() {
      m := make(map[string]struct{i int})
      m["foo"].i = 42
    }

Addressablility is defined in the spec as

The operand must be addressable, that is, either a variable, pointer indirection, or slice indexing operation; or a field selector of an addressable struct operand; or an array indexing operation of an addressable array. As an exception to the addressability requirement, x may also be a (possibly parenthesized) composite literal. If the evaluation of x would cause a run-time panic, then the evaluation of &x does too.

Values in a map are not addressable, but using pointers for indirection allow those values to be addressable

Here is a great explanation that really helped me understand the addressable vs unaddressable concept in golang

There are a number of important things that are not addressable. For example, values in a map and the return values from function and method calls are not addressable. The following are all errors: &m["key"]
&afunc()

&t.method()

How this relates to the initial question asked, is that everything on the left-hand-side is unaddressable as per the spec - and hence is an invalid use of the assignment operator

Each left-hand side operand must be addressable, a map index expression, or (for = assignments only) the blank identifier.

Tjad Clark
  • 552
  • 3
  • 17