Why do I get a "cannot assign" error when setting value to a struct as a value in a map?

Go

Go Problem Overview


New to Go. Encountered this error and have had no luck finding the cause or the rationale for it:

If I create a struct, I can obviously assign and re-assign the values no problem:

type Person struct {
 name string
 age int
}

func main() {
  x := Person{"Andy Capp", 98}
  x.age = 99
  fmt.Printf("age: %d\n", x.age)
}

but if the struct is one value in a map:

type Person struct {
     name string
     age int
 }

type People map[string]Person

func main() {
  p := make(People)
  p["HM"] = Person{"Hank McNamara", 39}
  p["HM"].age = p["HM"].age + 1
  fmt.Printf("age: %d\n", p["HM"].age)
}

I get cannot assign to p["HM"].age. That's it, no other info. http://play.golang.org/p/VRlSItd4eP

I found a way around this - creating an incrementAge func on Person, which can be called and the result assigned to the map key, eg p["HM"] = p["HM"].incrementAge().

But, my question is, what is the reason for this "cannot assign" error, and why shouldn't I be allowed to assign the struct value directly?

Go Solutions


Solution 1 - Go

p["HM"] isn't quite a regular addressable value: hashmaps can grow at runtime, and then their values get moved around in memory, and the old locations become outdated. If values in maps were treated as regular addressable values, those internals of the map implementation would get exposed.

So, instead, p["HM"] is a slightly different thing called a "map index expression" in the spec; if you search the spec for the phrase "index expression" you'll see you can do certain things with them, like read them, assign to them, and use them in increment/decrement expressions (for numeric types). But you can't do everything. They could have chosen to implement more special cases than they did, but I'm guessing they didn't just to keep things simple.

Your approach seems good here--you change it to a regular assignment, one of the specifically-allowed operations. Another approach (maybe good for larger structs you want to avoid copying around?) is to make the map value a regular old pointer that you can modify the underlying object through:

package main

import "fmt"

type Person struct {
	name string
	age  int
}

type People map[string]*Person

func main() {
	p := make(People)
	p["HM"] = &Person{"Hank McNamara", 39}
	p["HM"].age += 1
	fmt.Printf("age: %d\n", p["HM"].age)
}

Solution 2 - Go

The left side of the assignment must b "addressable".

https://golang.org/ref/spec#Assignments

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

and https://golang.org/ref/spec#Address_operators

>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 @twotwotwo's comment, p["HM"] is not addressable. but, there is no such definition show what is "addressable struct operand" in the spec. I think they should add some description for it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsbeamView Question on Stackoverflow
Solution 1 - GotwotwotwoView Answer on Stackoverflow
Solution 2 - GoJiang YDView Answer on Stackoverflow