4

Context

In the process of writing a generic diff and patch algorithm I faced a problem with reflection in go.

When I'm trying to patch in a slice I have no problem, reflect.ValueOf(&slice).Elem().Index(0).CanSet() returns true. This allows me to patch anything inside the slice element, be it a slice of primitive or of structs.

Problem

However when I attempt this with a map reflect.ValueOf(&map).Elem().MapIndex(reflect.ValueOf("key")).CanSet() returns false. This prevents me from attempting to do anythnin with the content of my map.

Examples

Slice

s := []string{"a", "b", "c"}

v := reflect.ValueOf(&s).Elem()

e := v.Index(1)

println(e.String())
println(e.CanSet())
e.Set(reflect.ValueOf("d"))

for _, v := range s {
    print(v, " ")
}

output :
b
true
a d c

Map

m := map[string]string{
    "a": "1",
    "b": "2",
    "c": "3"}

mv := reflect.ValueOf(&m).Elem()
println(mv.MapIndex(reflect.ValueOf("a")).CanSet())

output:
false

How could I get a modifiable value out of a map through reflection?

Thanks for your time.

Community
  • 1
  • 1
B. Delor
  • 51
  • 6

1 Answers1

6

This is not a limitation of the reflect package. Slice index expressions are addressable (e.g. &s[0] is valid for example), so slice elements obtained via reflection will be settable. Map index expressions are not addressable (e.g. &m["a"] is invalid), so values of keys obtained via reflection will not be settable. See related How to update map values in Go

Only addressable values are settable, attempting to "set" a non-addressable value could only modify a copy (and not the original value), so it's not allowed in the first place. Quoting from Value.CanSet():

CanSet reports whether the value of v can be changed. A Value can be changed only if it is addressable and was not obtained by the use of unexported struct fields.

If you want to change the value assigned to a key in a map using reflection, use the Value.SetMapIndex() method:

mv.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf("11"))
fmt.Println(m)

Output will be (try it on the Go Playground):

map[b:2 c:3 a:11]

Note: taking the address of a map index expression (and allowing to change a value using reflection) is not allowed because the internals of a map may change at any time (e.g. if new key-values are added to the map, the implementation may have to re-structure the way it stores the key-value pairs internally), so a possible pointer you would obtain by &m["a"] may not exist (or may point to another value) by the time you end up using it. To avoid such confusions and run-time panics, this is not allowed in the first place.

icza
  • 389,944
  • 63
  • 907
  • 827
  • Go says Map types are reference types, like pointers or slices. So why is it so that we can not Set the value of Maps Since it is also a reference type – Himanshu Mar 07 '18 at 09:17
  • Thank you for you answer, unfortunately I need to modify the content of the Value retrieved with MapIndex later in the code, this is why I need this Value settable. Is there a way to make a settable copy of a unsettable value? – B. Delor Mar 07 '18 at 09:19
  • @B.Delor No, there is not. As explained, a settable `reflect.Value` would prevent the runtime to operate the map normally, it could not change its internal structure, or the `reflect.Value` would become invalid / corrupt. – icza Mar 07 '18 at 09:20
  • Thanks @icza for explanation. So slice index will not going to change like maps since they are not making a copy of their values when you are looping over it. – Himanshu Mar 07 '18 at 09:21
  • @Himanshu Elements in a slice are not moved around "arbitrarily" by the runtime, while map values may be. – icza Mar 07 '18 at 09:21
  • This is why I was asking for a copy, it would be disconnected from the map, live on it's own and sporting a copy of the content of the map's accessed Value. Then I'll modify this copy and finally push it back in the map with SetMapIndex – B. Delor Mar 07 '18 at 09:22
  • Is there any go reference supporting this. – Himanshu Mar 07 '18 at 09:22
  • @B.Delor You can do that, but ultimately you have to call `Value.SetMapIndex()`. – icza Mar 07 '18 at 09:23
  • I'm wondering how to do that, I might be googling wrong but I'm gaving a hard time finding how to copy the the content of an unsettable value to a new, settable value ^^ – B. Delor Mar 07 '18 at 09:24
  • @B.Delor Read this answer: [How to copy an interface value in Go?](https://stackoverflow.com/questions/37851500/how-to-copy-an-interface-value-in-go/37851764#37851764) You may use `reflect.New()` to obtain a pointer to a newly allocated value of some type, wrapped in a `reflect.Value`, which will be settable. – icza Mar 07 '18 at 09:25
  • Thanks you a lot for your help – B. Delor Mar 07 '18 at 09:32