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

huangapple go评论92阅读模式
英文:

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

问题

对于你提供的代码,出现了一个错误:cannot assign to p["HM"].age。这是因为在Go语言中,当你将结构体作为map的值时,不能直接对其字段进行赋值操作。

解决这个问题的一种方法是创建一个在Person上的incrementAge函数,通过调用该函数并将结果赋值给map的键来实现递增年龄的目的,例如p["HM"] = p["HM"].incrementAge()

至于为什么会出现这个错误以及为什么不允许直接对结构体值进行赋值,我认为这是Go语言设计的一种限制。在Go语言中,结构体是值类型,而map中的值是不可寻址的。因此,直接对map中的结构体字段进行赋值是不被允许的。通过使用函数来间接修改结构体的字段值,可以绕过这个限制。这样设计的好处是可以确保map的一致性和安全性。

英文:

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?

答案1

得分: 183

p["HM"]不是一个普通的可寻址值:哈希表可以在运行时增长,然后它们的值在内存中移动,旧的位置变得过时。如果将映射中的值视为普通的可寻址值,那么map实现的内部细节将被暴露出来。

因此,p["HM"]在规范中被称为稍微不同的东西,称为"map index expression";如果你在规范中搜索"index expression"这个短语,你会看到你可以对它们进行某些操作,比如读取它们、对它们赋值,并在递增/递减表达式中使用它们(对于数值类型)。但并不是所有的操作都可以进行。他们本可以选择实现更多的特殊情况,但我猜他们之所以没有这样做,是为了保持简单。

你的方法在这里看起来不错--你将它更改为普通的赋值,这是其中一个特别允许的操作。另一种方法(也许适用于你想避免复制的较大结构体?)是将映射值设置为一个普通的旧指针,通过它可以修改底层对象:

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)
}
英文:

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)
}

答案2

得分: 5

赋值语句的左侧必须是“可寻址”的。

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

每个左操作数必须是可寻址的、映射索引表达式,或者(仅对于=赋值)是空白标识符。

而且 https://golang.org/ref/spec#Address_operators

操作数必须是可寻址的,也就是说,要么是变量、指针间接引用或切片索引操作;或者是可寻址结构操作数的字段选择器;或者是可寻址数组的数组索引操作。

根据 @twotwotwo 的评论,p["HM"] 不是可寻址的。
但是,在规范中没有明确定义“可寻址的结构操作数”,我认为他们应该为此添加一些说明。

英文:

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.

huangapple
  • 本文由 发表于 2015年9月24日 08:29:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/32751537.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定