Reputation: 352
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.
Upvotes: 11
Views: 8832
Reputation: 91253
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.
Upvotes: 2
Reputation: 1229
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.
Upvotes: 16
Reputation: 12023
Technically, according to the language reference, the expression testmap["key"].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["key"]
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.
Upvotes: 1
Reputation: 23567
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 pointers to A, you'd be in business. This would compile:
testMap := make(map[string]*A)
testMap["key"] = &A{}
testMap["key"].B = "test2"
The reason that this works is that dereferencing and assigning to a pointer value does have an effect.
Upvotes: 5