Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reflect thinks struct Value is also a ptr?

Tags:

reflection

go

I have a data structure like this demo. As you can see, foo has an embedded pointer to bar:

type foo struct {
    *bar
}

type bar struct {
    S []byte
}

And I'm using the reflect package like this:

func test(x interface{}) {

    var v = reflect.ValueOf(x)

    if v.Kind() == reflect.Struct {
        fmt.Println("was a struct")

    // panic: reflect: call of reflect.Value.Elem on struct Value
    //  v = v.Elem()

    // panic: reflect: call of reflect.Value.Field on ptr Value
        v = v.FieldByName("S")
    }
}

func main() {
    var f foo
    test(f)
    fmt.Println(string(f.S))
}

So v.Kind() is recognized as a reflect.Struct, but if I try to treat it like a struct by using .FieldByName("S"), it panics because it thinks v is a ptr.

So then if I try to treat it like a ptr by calling .Elem(), it panics because it thinks v is a struct.

I've tried reflect.Indirect(), as well as a few other things, but I can't figure out how to get the field of an embedded pointer.

Is there a way to get the reflect.Value representation from an embedded pointer to a struct?

Demo: http://play.golang.org/p/n0eea6XW3I


EDIT: Also tried v = v.FieldByName("bar"), but got:

panic: runtime error: invalid memory address or nil pointer dereference

like image 855
cookie monster Avatar asked Mar 21 '23 10:03

cookie monster


1 Answers

The first thing we need to realize is that the line var f foo is equivalent to f := foo{}. This initializes the internal field bar (of type *bar) to its zero value... nil. The behavior of embedded types and reflect seems to be that it treats the embedded type's fields as fields of the type itself. So when you request v.FieldByName("S") it's trying to find that field in f's member, bar, which is nil.

You're trying to do this (*f.bar).S. (In Go the explicit pointer dereference isn't needed, but it makes my point). Now the question is: if you change is to v.FieldByName("bar") why does it give an error? Same reason.

Look closely at the stack trace, the FieldByName line no longer crashes, the line that crashes is fmt.Println(string(f.S)). Again, semantically you're doing (*f.bar).S. But the member "bar" is nil, so you are, in fact, doing a nil pointer dereference.

You can fix both errors by changing var f foo to f := foo{&bar{}}.

like image 61
Linear Avatar answered Mar 28 '23 18:03

Linear