Cannot assign to struct field in a map
GoGo Problem Overview
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.
Go Solutions
Solution 1 - Go
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.
Solution 2 - Go
First, for this question, the solution in this post https://stackoverflow.com/questions/32751537/why-do-i-get-a-cannot-assign-error-when-setting-value-to-a-struct-as-a-value-i 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.
Solution 3 - Go
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.
Solution 4 - Go
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.)
Solution 5 - Go
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
> 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.