user2089648
user2089648

Reputation: 1416

Golang reflection.Value behaviour

I'm currently getting desperate over the behaviour of golangs reflect package, which to me doesn't seem consistent at all.

1) As far as I understand it, a reflect.Value seems to carry a pointer to the underlying value. E.g. if I call

var s string
v1 := reflect.ValueOf(&s).Elem()
v2 := v1
v2.SetString("Hello World!")
fmt.Println(s)

It prints me "Hello World!". However, this doesn't seem to hold true for a reflect.Value obtained by a call to Field().

val := ... //Assign a reflect.Value to it
nextval := val.Field(0) //Make sure that Field exists and is of type map
nextval = reflect.MakeMap(reflect.MapOf(KEY, ELEM))
nextval.SetMapIndex(Some_value_of_type_KEY, Something_of_type_ELEM)
fmt.Println(nextval.MapKeys()
fmt.Println(val.Field(index).MapKeys())

This prints

[Some_value_of_type_KEY]
[]

which is a major annoyance. Anyone knows why this is the case?

===================================================

2) Consider the function

func Test(v interface{}) {
    val := reflect.ValueOf(v)
    if val.Kind() != reflect.Struct {
        fmt.Println("It is a struct")
    }
}

If I call it with any struct as an argument it prints "This is a struct". However, I won't be able to assign new values to stuff inside v by using val, due to the value not being addressable. Working around by the following:

func Test(v interface{}) {
    val := reflect.ValueOf(&v).Elem()
    if val.Kind() != reflect.Struct {
        fmt.Println("This never get's printed!")
    }
}

According to the doc, I would assume, that by taking the '&' I use a pointer to v and by the call of Elem() I get the element it points to, therefore val.Kind() should still return the same thing. It doesn't. val.Kind() now is a reflect.Interface.

Is there a way of not having to go

valForTestingKind := reflect.ValueOf(v)
valForSettingNewValue := reflect.ValueOf(&v).Elem()

as this somehow feels wrong.

Upvotes: 0

Views: 4098

Answers (4)

Stephen Weinberg
Stephen Weinberg

Reputation: 53508

Part 1:

By assigning to nextval, you are breaking its association with the original val. Instead, use the Set() method.

nextval.Set(reflect.MakeMap(reflect.MapOf(KEY, ELEM)))

Set() is the equivalent of assignment in the reflection world. Of course, you must make sure it is assignable using reflect.ValueOf(&v).Elem() as you do in your first code example.


Part 2:

The issue here is that you have another level of indirection. v is of type interface{} and has a concrete value whose type is of Kind struct. Just like with every function that accepts an interface typed parameter, when you call reflect.ValueOf, the parameter is automatically converted to that type. However, converting an interface to another interface results in the concrete value being reboxed in the new interface type. The information of the type before it was reboxed is lost. As an example, a function that accepts an io.Writer would not know that the calling function considered it an io.ReaderWriter.

In this context, it means that reflect.ValueOf cannot tell if you passed an os.File (some struct) or a file boxed in an interface{}. It assumes you passed an os.File and shows you the Kind "struct".

However, when you pass a pointer to an interface{}, you are passing an interface{} variable that can be modified. You are not passing the underlying concrete type and that has important consequences. You can .Set() anything, not just what the original concrete type allows. You also can't edit individual fields as anything in an interface{} is not assignable. If the concrete type is in fact a pointer, you can do a fourth dereference (.Elem()) and modify fields from there.

So, what does this mean in terms of code?

//let v = an interface{} with a concrete type of SomeStruct
val := reflect.ValueOf(&v).Elem()
fmt.Println(val.Elem().Kind())                // struct
val.Elem().Field(0).Set(10)                   // PANIC! Field isn't assignable.
val.Set("a string which is not a SomeStruct")
fmt.Println(val.Elem().Kind())                // string

I made an example here: http://play.golang.org/p/6MULn3KoNh

Upvotes: 2

zzzz
zzzz

Reputation: 91429

You have not shown a complete and compilable code. Do you pass a pointer to a struct or do you pass the struct by value? In the later case reflection cannot mutate it.

Upvotes: 0

David Grayson
David Grayson

Reputation: 87516

I want to talk about your second block of code:

val := ... //Assign a reflect.Value to it
nextval := val.Field(0) //Make sure that Field exists and is of type map
nextval = reflect.MakeMap(reflect.MapOf(KEY, ELEM))
nextval.SetMapIndex(Some_value_of_type_KEY, Something_of_type_ELEM)
fmt.Println(nextval.MapKeys()
fmt.Println(val.Field(index).MapKeys())

On the third line, you are reassigning a new, different object to the variable nextval. Shouldn't you call some kind of setting method on nextval instead of reassigning it? In your first example, you called SetString but in this example you are just reassigning the variable and that might be why the behavior is different. After you reassign the variable, nextval will no longer be connected in any way to val.Field(0). Also, what is index?

If this does not explain your problem, please edit the question to contain a short, self-contained, correct, compilable example ( SSCCE ). I want to be able to post it into the text box on the front page of golang.org in order to see the problem. You should always post an SSCCE when possible.

Upvotes: 1

Related Questions