Map of structs vs array of structs in Go

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

Map of structs vs array of structs in Go

问题

假设我有一个简单的结构体 A,其中包含一个字符串属性 B

type A struct {
    B string
}

下面的代码使用了一个 A 类型的数组:

testArray := []A{A{}}
testArray[0].B = "test1"
fmt.Println(testArray[0].B)

预期输出结果是 "test1"。

但是下面的代码看起来同样简单:

testMap := make(map[string]A)
testMap["key"] = A{}
testMap["key"].B = "test2"
fmt.Println(testMap["key"].B)

输出结果却不是 "test2",而是会导致以下错误:

cannot assign to testMap["key"].B

那么,为什么在映射中对子属性进行赋值会导致错误,而在数组中对子属性进行赋值却能正常工作呢?我想知道为什么映射不起作用,数组却起作用,并且我也很想猜测一下为什么设计语言时会在这两种数据结构之间存在这种差异。

英文:

Let's say I have a simple struct a with a string property b:

type A struct {
	B string
}

The following code using an array of A types:

testArray := []A{A{}}
testArray[0].B = "test1"
fmt.Println(testArray[0].B)

Will print out "test1" as expected.

But this code that seems equally simple:

testMap := make(map[string]A)
testMap["key"] = A{}
testMap["key"].B = "test2"
fmt.Println(testMap["key"].B)

Will not print out "test2" but instead will result in the following error:
> cannot assign to testMap["key"].B

So, why does assigning to the subproperty in a map result in an error while assigning to the subproperty in an array work as expected? I want to know both why this doesn't work for maps AND why it does work for arrays. I would also love some speculation on why they designed the language with this difference between the two data structures.

答案1

得分: 14

我在邮件列表上进行了详细回答,但简短的解释是这样的:这种方法不起作用,因为映射条目是不可寻址的。这意味着你不能获取映射中条目的地址。这是因为向映射中添加新值可能会导致映射条目重新排序,从而地址发生变化。由于不能获取映射中条目的地址,所有映射操作都使用整个值:从映射中复制整个值,向映射中添加整个值。在映射中对结构体的一个字段进行赋值将需要进行读取-修改-写入操作,而映射不支持这种操作(虽然它们可以支持,但不支持会有一些代价)。

数组和切片中的元素是可寻址的,因为它们在创建后不会移动。

英文:

I answered at some length on the mailing list, but the short explanation is that this doesn't work because map entries are not addressable. What that means is that you can't take the address of an entry in a map. And that is because adding a new value to a map may cause map entries to shift around, so that the addresses change. Because you can't take the address of an entry in a map, all map operations use whole values: copy a whole value out of a map, add a whole to a map. Assigning to one field of a struct in a map would require a read-modify-write operation, that maps do not support (they could, but they don't, and there is a cost to supporting them).

Elements in arrays and slices are addressable because they do not move around after they have been created.

答案2

得分: 5

问题在于在地图示例中,testMap["key"]返回的是一个字面量,而不是一个指针。这意味着修改它是没有意义的,所以编译器不允许这样做。它基本上等同于:

v := testMap["key"]
v.B = "test2"

...然后再也不使用v了。它没有任何效果。它等同于根本不执行这两行代码。这就是为什么编译器不允许你这样做的原因。另一方面,如果你将它定义为指向A的指针的地图,那么就可以编译通过:

testMap := make(map[string]*A)
testMap["key"] = &A{}
testMap["key"].B = "test2"

这样做的原因是解引用并对指针值进行赋值是有效果的。

英文:

The problem is that in the map example, testMap["key"] returns a literal, not a pointer. This means that modifying it is meaningless, so the compiler disallows it. It's basically equivalent to:

v := testMap["key"]
v.B = "test2"

... and then never using v again. It has no effect. It's equivalent to never executing those two lines in the first place. That's why the compiler won't let you do it. On the other hand, had you made it a map of <i>pointers</i> to A, you'd be in business. This <i>would</i> compile:

testMap := make(map[string]*A)
testMap[&quot;key&quot;] = &amp;A{}
testMap[&quot;key&quot;].B = &quot;test2&quot;

The reason that this works is that dereferencing and assigning to a pointer value <i>does</i> have an effect.

答案3

得分: 2

数组元素是lvalue。对于映射(maps)来说,情况会稍微复杂一些。在以下代码中:

m := map[T]U{}
m[T(expr)] = U(expr)

左侧的 m[T(expr)] 是一个 lvalue。然而,在以下代码中:

type U struct{
        F V
}

m := map[T]U{}
m[T(expr)].F = 34

左侧的 m[T(expr)].F 不再是一个 lvalue。第一部分 m[T(expr)] 会求值为一个 U 类型的实例。该实例是“浮动”的,它不再有固定位置。给它的字段赋值肯定是一个错误,所以编译器会报错。

这与以下代码的区别基本相同:

var v U
v.F = 42 // 正确
U{}.F = 42 // 错误

为了解决这个问题,你可以使用指向结构体的指针:

m := map[T]*U{}
m[T(expr)].F = 42

映射首先返回一个指向 U 的指针,然后用它来设置字段的值。

英文:

Array element is an lvalue. With maps it's a bit more complex. In:

m := map[T]U{}
m[T(expr)] = U(expr)

the LHS m[T(expr)] is an lvalue. However, in:

type U struct{
        F V
}

m := map[T]U{}
m[T(expr)].F = 34

the LHS m[T(expr)].F is not an lvalue anymore. The first part, m[T(expr)] evaluates to an instance of type U. That instance is "floating", it has no home anymore. Assigning something to its fields is certainly a mistake, so the compiler yells.

It's more or less the same as the difference between:

var v U
v.F = 42 // ok
U{}.F = 42 // not ok

Top resolve the problem, you can use pointer to a struct:

m := map[T]*U{}
m[T(expr)].F = 42

The map first yields a pointer to U which is then used to set the field.

答案4

得分: 1

根据语言参考,表达式testmap["key"].B在技术上不是可寻址的,因此不能用作赋值语句的左操作数。

所以问题可能需要转变为:为什么这个表达式不可寻址?对此我还不太确定...

啊,原因是testmap["key"]返回给我们的是结构体的副本。修改该副本可能不是我们想要做的,因为它不会影响映射中的原始结构体。

英文:

Technically, according to the language reference, the expression testmap[&quot;key&quot;].B is not addressable, so it can't be used as the left hand side of an assignment.

So the question might need to be shifted to: why is that expression not addressable? I'm not quite sure about that yet...

... ah. It's because testmap[&quot;key&quot;] is giving us back a copy of the structure. Mutating that copy is probably not what we want to do, since it won't affect the original structure in the map.

huangapple
  • 本文由 发表于 2013年8月6日 22:29:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/18083044.html
匿名

发表评论

匿名网友

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

确定