无法在映射中为结构字段赋值。

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

Cannot assign to struct field in a map

问题

我有这样的数据结构:

type Snapshot struct {
  Key   string
  Users []Users
}

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

// 然后进行初始化
snapshots["test"] = Snapshot {
  Key: "testVal",
  Users: make([]Users, 0),
}

Users 是另一个结构体。

然后,当我尝试像这样向 Users 切片中添加一些新的 Users 值时:

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

我一直得到这个错误:

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

我还尝试了这里的解决方法 https://github.com/golang/go/issues/3117,像这样:

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

但是没有成功,仍然是完全相同的错误。

我还尝试了使用指针声明 map,所以:snapshots := make(map[string]*Snapshot, 1),但仍然没有成功。

英文:

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:

<!-- language: lang-none -->

cannot assign to struct field snapshots[&quot;test&quot;].Users in map

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

tmp := snapshots[&quot;test&quot;].Users
tmp = append(tmp, user)
snapshots[&quot;test&quot;].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.

答案1

得分: 84

对于那些寻找更简单示例的人:

这是错误的:

type myStruct struct{
   Field int
}

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

   myMap["key"].Field = 5
}

因为myMap["key"]不是“可寻址”的。

这是正确的:

type myStruct struct{
   Field int
}

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

   // 首先我们获取条目的“副本”
   if entry, ok := myMap["key"]; ok {

       // 然后我们修改副本
       entry.Field = 5
    
       // 然后我们重新分配map条目
       myMap["key"] = entry
   }

   // 现在“key”.Field是5
   fmt.Println(myMap) // 输出 map[key:{5}]
}

这里有一个可运行的示例。

英文:

For those looking for a simpler example:

This is wrong:

type myStruct struct{
   Field int
}

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

   myMap[&quot;key&quot;].Field = 5
}

Because myMap[&quot;key&quot;] is not "addressable".

This is correct:

type myStruct struct{
   Field int
}

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

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

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

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

Here you have a working example.

答案2

得分: 15

对于我的用例,我需要经常更新条目。因此,修改副本并将其重新分配给映射条目将非常低效。另一种替代方法是使用指向结构体的指针。(我知道它不适用于所有用例,但如果你的用例足够灵活,可以使用结构体或指向结构体的指针...)

type bigStruct struct {
    val1 int
    val2 bool
    val3 string
}

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

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

// 现在可以正常修改条目
newMap["struct1"].val1 = 2
newMap["struct1"].val2 = false
newMap["struct1"].val3 = "example2"

在此处查看完整代码:这里

英文:

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[&quot;struct1&quot;] = &amp;bigStruct{1, true, &quot;example1&quot;}

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

See the full code here.

答案3

得分: 14

首先,对于这个问题,在这个帖子中的解决方案https://stackoverflow.com/questions/32751537/why-do-i-get-a-cannot-assign-error-when-setting-value-to-a-struct-as-a-value-i完全有效。

然后,最后终于弄清楚了为什么在我已经改用指针的情况下,我的情况仍然不起作用,参考下面的非常简单的代码:

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

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

你认为输出会是什么?我简单地认为它们都会是3,但实际上对于映射,输出将是0

然后在映射初始化过程中,我使用了一个循环,并使用了这个值len(snapshots),这意味着初始化过程永远不会运行...

是的,这就是原因。

英文:

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.

答案4

得分: 2

我用循环使用结构体映射的方法如下所示:

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

输出应该是:

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

这样可以将多个值分配给结构体映射,但不是追加方式。另外,你也可以通过以下方式让映射引用自身的值:

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

这样输出应该是:

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

请注意,当我们引用空结构值时不会引发错误,这是因为当我们初始化结构体时,其值会以空值而不是nil值开始(int为0,string为""等)。

英文:

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{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}

  for i := 0; i &lt; 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{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;}
  for i := 0; i &lt; 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, &quot;&quot; for string, etc.)

答案5

得分: 1

无法执行Asker尝试的操作的原因是因为无法寻址。

根据规范中的错误信息,你收到的确切错误是_UnaddressableFieldAssign,当你尝试对一个不可寻址的值进行赋值时会引发该错误。

Addressablility在规范中被定义为:

操作数必须是可寻址的,即变量、指针间接引用或切片索引操作;或者是可寻址结构操作数的字段选择器;或者是可寻址数组的数组索引操作。作为对可寻址要求的例外,x也可以是一个(可能带括号的)复合字面量。如果对x的求值会导致运行时恐慌,那么对&x的求值也会如此。

映射中的值是不可寻址的,但使用指针进行间接引用可以使这些值可寻址。

以下是一篇很好的解释,帮助我理解了Go语言中可寻址与不可寻址的概念:

有一些重要的东西是不可寻址的。例如,映射中的值以及函数和方法调用的返回值都是不可寻址的。以下都是错误的示例:
&m["key"]
&afunc()
&t.method()

这与最初提出的问题有关,根据规范,左侧的所有内容都是不可寻址的,因此是对赋值运算符的无效使用。

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

英文:

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[&quot;foo&quot;].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.

huangapple
  • 本文由 发表于 2017年3月5日 14:42:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/42605337.html
匿名

发表评论

匿名网友

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

确定