Get pointer to value using reflection

后端 未结 4 750
感动是毒
感动是毒 2020-12-28 21:30

I have a function that iterates through all fields of an interface passed as parameter. In order to achieve this is I am using reflection. The issue is that I do not know ho

相关标签:
4条回答
  • 2020-12-28 21:40

    The reason Interface() doesn't work is because of the interface wrapper it returns. To give an idea of what's going on, let's look at what we're doing without reflection:

    type MyStruct struct {
        F Foo
    }
    
    type Foo struct {
        i int
    }
    
    func ExtractField(ptr *MyStruct) interface{} {
        return ptr.F
    }
    
    func main() {
        ms := &MyStruct{Foo{5}}
        f := ExtractField(ms).(Foo) // extract value
        f.i = 19
        fmt.Println(f, ms.F)            // ???
        fmt.Println(&f == &ms.F)        // Not the same!
    }
    

    (Playground)

    However, think about the interface{} this returns. What is it wrapping? The value of ptr.F -- that is, a copy of it. This is what value.Interface does, it returns you the interface{} wrapping the field. There is no longer any pointer metadata, it's completely detached from the original struct.

    As you'll note, passing a value directly to reflect.ValueOf will always return false for CanAddr for the "top tier" -- because that address is meaningless, since it would give you the address of the copy of the value, changing it wouldn't really mean anything. (Keep in mind that pointers are values too -- if you want the address of a pointer-valued field like *Foo, you're really looking for **Foo).

    So, in our example above, if we were to naively pass in reflect.ValueOf(ExtractField(ms)) we'd get the ValueOf f, which not only doesn't have the address you want -- it isn't even addressable according to reflect because it would never give a valid address as far as reflect is concerned (the only address it could give you is the address of the internal value copy in the Value struct).

    So why does passing the Value down the rabbit hole work? Well, the only real way to say it is that reflect.Value maintains the necessary metadata when you use Elem and Field, while the interface{} cannot. So while the reflect.Value may look like:

    // Disclaimer: not the real structure of a reflect.Value
    type Value struct {
        fieldAddress uintptr
        value        Foo
    }
    

    All it can give you is this

    // Again, an abstraction of the real interface wrapper 
    // just for illustration purposes
    type interface{} struct {
        value Foo
    }
    
    0 讨论(0)
  • 2020-12-28 21:40

    I went down a reflect rabbit hole today, learned a lot from studying this code and LinearZoetrope's answer, thank you. I came to a different conclusion though about your problem which led to perhaps a more straightforward solution:

    1) You're passing in a pointer to a struct when you originally call the function, but...

    2) When you recurse by calling 'InspectStruct(valueField.Interface())', rather than passing the embedded structure by pointer, you're passing it by value.

    Since you're passing by value, go will create a temporary and won't let you take the address. Instead, when you recurse, call valueField.Addr().Interface(), which will pass a pointer to the embedded struct.

        if valueField.Kind() == reflect.Struct {
    -     InspectStruct(valueField.Interface())
    +     InspectStruct(valueField.Addr().Interface())
        }
    

    With this change, I get the output you are expecting:

    Field Name: Id,  Field Value: 1,     Address: 842350527552  , Field type: int   , Field kind: int
    Field Name: F,   Field Value: {2 {3}},   Address: 842350527560  , Field type: lib.V , Field kind: struct
    Field Name: Id,  Field Value: 2,     Address: 842350527560  , Field type: int   , Field kind: int
    Field Name: F,   Field Value: {3},   Address: 842350527568  , Field type: lib.Z , Field kind: struct
    Field Name: Id,  Field Value: 3,     Address: 842350527568  , Field type: int   , Field kind: int
    
    0 讨论(0)
  • 2020-12-28 21:41

    Passing reflect.Value instead of interface{} seems to fix the problem, however I don't know why valueField.Interface() doesn't work.

    Working example : http://play.golang.org/p/nleA2YWMj8

    func InspectStructV(val reflect.Value) {
        if val.Kind() == reflect.Interface && !val.IsNil() {
            elm := val.Elem()
            if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
                val = elm
            }
        }
        if val.Kind() == reflect.Ptr {
            val = val.Elem()
        }
    
        for i := 0; i < val.NumField(); i++ {
            valueField := val.Field(i)
            typeField := val.Type().Field(i)
            address := "not-addressable"
    
            if valueField.Kind() == reflect.Interface && !valueField.IsNil() {
                elm := valueField.Elem()
                if elm.Kind() == reflect.Ptr && !elm.IsNil() && elm.Elem().Kind() == reflect.Ptr {
                    valueField = elm
                }
            }
    
            if valueField.Kind() == reflect.Ptr {
                valueField = valueField.Elem()
    
            }
            if valueField.CanAddr() {
                address = fmt.Sprintf("0x%X", valueField.Addr().Pointer())
            }
    
            fmt.Printf("Field Name: %s,\t Field Value: %v,\t Address: %v\t, Field type: %v\t, Field kind: %v\n", typeField.Name,
                valueField.Interface(), address, typeField.Type, valueField.Kind())
    
            if valueField.Kind() == reflect.Struct {
                InspectStructV(valueField)
            }
        }
    }
    
    func InspectStruct(v interface{}) {
        InspectStructV(reflect.ValueOf(v))
    }
    
    0 讨论(0)
  • 2020-12-28 21:56

    Answer of @OneofOne is perfect, but it is better to add one additional check

    if valueField.IsValid() {
            fmt.Printf("Field Name: %s, Field Value: %v, Address: %v, Field type: %v, Field kind: %v\n", typeField.Name,
                valueField.Interface(), address, typeField.Type, valueField.Kind())
        }
    

    it is needed because sometimes you can ask for interface from a zero value struct. Ones it happens, it will panic.

    0 讨论(0)
提交回复
热议问题