B. Delor
B. Delor

Reputation: 61

Reflect accessed map does not provide a modifiable value

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.

Upvotes: 4

Views: 2584

Answers (1)

icza
icza

Reputation: 417592

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.

Upvotes: 6

Related Questions