52

Assuming the following

type User struct {
    name string
}

users := make(map[int]User)

users[5] = User{"Steve"}

Why isn't it possible to access the struct instance now stored in the map?

users[5].name = "Mark"

Can anyone shed some light into how to access the map-stored struct, or the logic behind why it's not possible?

Notes

I know that you can achieve this by making a copy of the struct, changing the copy, and copying back into the map -- but that's a costly copy operation.

I also know this can be done by storing struct pointers in my map, but I don't want to do that either.

wasmup
  • 14,541
  • 6
  • 42
  • 58
gwelter
  • 1,054
  • 11
  • 14
  • 1
    There's a good discussion of this at http://golang.org/doc/effective_go.html#pointers_vs_values and also http://golang.org/doc/faq#Pointers – Intermernet Jul 03 '13 at 04:08
  • 2
    Intermernet, thanks for those resources but I don't see anything pertaining to in-place edits of map structs. Perhaps I am missing something? – gwelter Jul 03 '13 at 16:02

2 Answers2

73

The fundamental problem is that you can't take the address of an item within a map. You might think the compiler would re-arrange users[5].name = "Mark" into this

(&users[5]).name = "Mark"

But that doesn't compile, giving this error

cannot take the address of users[5]

This gives the maps the freedom to re-order things at will to use memory efficiently.

The only way to change something explicitly in a map is to assign value to it, i.e.

t := users[5]
t.name = "Mark"
users[5] = t

So I think you either have to live with the copy above or live with storing pointers in your map. Storing pointers have the disadvantage of using more memory and more memory allocations, which may outweigh the copying way above - only you and your application can tell that.

A third alternative is to use a slice - your original syntax works perfectly if you change users := make(map[int]User) to users := make([]User, 10)

Eric
  • 295
  • 3
  • 6
Nick Craig-Wood
  • 52,955
  • 12
  • 126
  • 132
  • Thanks Nick. The slice alternative will come in handy. It still seems like making an in-place change should be possible as long as I'm not trying to store a pointer to the map cell for later use. I'm new to Go, but aren't slices also pointing to an underlying array which can change and sometimes move in memory? – gwelter Jul 03 '13 at 15:58
  • The underlying array that a slice points to can't move so you can take addresses of its parts. – Nick Craig-Wood Jul 04 '13 at 14:48
  • This is what I was thinking of -- "The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself" -- from http://golang.org/pkg/builtin/#append – gwelter Jul 06 '13 at 16:02
  • 4
    I still don't understand why I can't assign to a field of the struct in a map while I can still access the field. I can only read but not write the field. Can you please clarify why reading the field is not using the address?Thanks – zyfo2 Feb 23 '17 at 15:08
  • 2
    @zyfo2: there's no fundamental reason the compiler and runtime *couldn't* arrange for `m[key].field = value` to compile into `temp := m[key]; temp.field = value; m[key] = temp`, or an optimized version thereof—and we'd almost certainly *want* the optimized version, which really would require some help from the runtime—but the language spec simply does not say that Go compilers must do that, and they don't. – torek Dec 16 '19 at 08:52
  • 1
    Meanwhile, it might help to realize that current implementations turn `temp := m[key]` into the compile-time equivalent of: `var temp T; memoryCopy(unsafe.Pointer(&temp), mapLookup(m, unsafe.Pointer(&key)), unsafe.Sizeof(unsafe.Pointer(&temp)))`. That is, the map lookup operation returns a pointer to the found element, or to a zeroed out element. The compiler copies the bytes from this returned pointer into the temporary variable as quickly as possible, before the returned pointer becomes invalid. – torek Dec 16 '19 at 08:55
  • A fancy optimized version of `m[key].field = value` would likely invoke a new third runtime function that returns the address of the existing or newly-inserted-zero-value `m[key]` object in the map, which the compiler could then offset according to the field, and copy a value into this target pointer as quickly as possible, before the returned pointer becomes invalid. This has a certain symmetry with the existing `temp := m[key]` but requires writing this third runtime function, plus a bit of compiler source to use it. – torek Dec 16 '19 at 08:59
  • My take on this is that invoking addressability of map value into the argument of mutating values kept in maps "in place" is a red herring; here's why: a map read operation, `v = m[i]`, is defined as returning _the value_ of the `i`th element of map `m`, and `m[i] = v` is defined as setting _the value_ of that element. In both cases, the value is either copied out, or copied in, completely. Hence if the language spec would permit `m[i].f = v` that would essentially mean "fetch the value of `m[i]`, assign `v` to its field `f` and _throw the resulting value away"._ – kostix Dec 16 '19 at 12:50
  • 1
    Sure, the language creators would turn around and do a non-obvious thing of stating that while a "regular" map read, `v = m[i]`, would copy the value out, the "modify field" access, `m[i].f = v` would either require taking the address of the map's element at `i` or defined to be a three-step operation—copy the value out, mutate the field, copy the value in. The former would logically require bringing the ability to address map elements to the language's proper, and the latter would imply non-obvious runtime costs. Also, how would you define the semantics of `m[i].a, m[i].b = m[i].c, m[i].d`? – kostix Dec 16 '19 at 12:55
  • 2
    Hence, I think the language designers opted to implement the clearest possible semantics: "maps contain values, operation on map elements load and store values of the corresponding types; map elements are not addressable to permit maps move their storage around'. – kostix Dec 16 '19 at 12:58
  • @kostix: Indeed, the "multiple mentions of `m[i].field` on the left hand side" situation imposes a lot of requirements here—especially if we allow `m[i].field1, m[j].field2` on the left—and is a very good argument *against* trying to define semantics for this. I will maintain that it is possible to *do* it, but that the result will be ugly no matter what one picks. (For instance, we could say that if `m[j]` is not already in `m` after looking up `m[i]` with intent to write on any or all of it, the operation panics. That makes the implementation easy, but the feature itself far less useful.) – torek Dec 16 '19 at 18:11
  • do other languages like c# dictionary does the same like copying the element and change the value and recopy into the new memory? @NickCraig-Wood – VINNUSAURUS Oct 26 '20 at 03:24
12
  1. Maps are typically sparsely filled hash tables which are reallocated when they exceed the threshold. Re-allocation would create issues when someone is holding the pointers to the values
  2. If you are keen on not creating the copy of the object, you can store the pointer to the object itself as the value
  3. When we are referring the map, the value returned is returned "returned by value", if i may borrow the terminology used in function parameters, editing the returned structure does not have any impact on the contents of the map
pr-pal
  • 3,248
  • 26
  • 18