Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot assign to struct field in a map

Tags:

go

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.

like image 498
lnshi Avatar asked Mar 05 '17 06:03

lnshi


3 Answers

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.

like image 98
Jairo Lozano Avatar answered Nov 15 '22 00:11

Jairo Lozano


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.

like image 13
lnshi Avatar answered Nov 15 '22 01:11

lnshi


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.

like image 9
Ankit Kumar Avatar answered Nov 15 '22 01:11

Ankit Kumar