4

How can I check nested pointer easily?

type inner1 struct {
    value string
}

type inner2 struct {
    value *inner1
}

type outter struct {
    value *inner2
}

I have data like this:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
} 

If I want to get data from this, I need to check for nil pointer.

func printValue(o *outter) (string, bool) {
    if o.value != nil {
        v1 := o.value
        if v1.value != nil {
            v2 := v1.value
            return v2.value, true
        }
    }
    return "", false
}

Yea, I can do this. But in my real scenario, this nested pointer part is much longer. I will prefer another way.

I have checked this answer, Test for nil values in golang nested stucts. But I need alternative.

How can I accomplish this. Any efficient and better option?

Shahriar
  • 13,460
  • 8
  • 78
  • 95

4 Answers4

2

Panic and recover is for exceptional cases. Checking if a pointer is nil is usually not, so you should stay away from it. Checking if a pointer is nil using an if statement is much cleaner than to introduce a deferred function with recover(). Especially as this recover() will stop all other kinds of panic, even those that would result from other reasons than trying to dereference a nil pointer. (Using defer and recover() is also slower than a simple nil check using if.)

If the data is always required to be a non-nil value, you should consider not using a pointer for it. And if the data is always required to be non-nil but for some reason you are required to use a pointer, then this may be a valid case to let your code panic if any of the pointers are still nil.

If it may be nil, then you have to check the nil case and handle it appropriately. You have to be explicit about this, Go doesn't help you omit this check.

To avoid having to check nil in every place, a utility function or method is reasonable. Note that methods can be called even if the receiver is nil which may be useful in such cases.

For example you may attach the following methods:

func (i *inner1) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Get() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value.Get()
}

func (o *outter) Get() (string, bool) {
    if o == nil {
        return "", false
    }
    return o.value.Get()
}

Note that each Get() method requires to check a single pointer, doesn't matter how complex the data structure is.

Testing it:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Get())

o.value.value = nil
fmt.Println(o.Get())

o.value = nil
fmt.Println(o.Get())

o = nil
fmt.Println(o.Get())

Output (try it on the Go Playground):

I need this data true
 false
 false
 false

The above solution hides the internals of outter which is useful for those using outter (doesn't need updating the clients if internals of outter change, just the outter.Get() method).

A similar approach would be to add methods that only return the field of the receiver struct:

func (i *inner1) Value() (string, bool) {
    if i == nil {
        return "", false
    }
    return i.value, true
}

func (i *inner2) Inner1() *inner1 {
    if i == nil {
        return nil
    }
    return i.value
}

func (o *outter) Inner2() *inner2 {
    if o == nil {
        return nil
    }
    return o.value
}

This approach requires clients to know internals of outter, but similarly it does not require any nil checks when using it:

o := &outter{
    value: &inner2{
        value: &inner1{
            value: "I need this data",
        },
    },
}
fmt.Println(o.Inner2().Inner1().Value())

o.value.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o.value = nil
fmt.Println(o.Inner2().Inner1().Value())

o = nil
fmt.Println(o.Inner2().Inner1().Value())

Output is the same. Try this one on the Go Playground.

icza
  • 389,944
  • 63
  • 907
  • 827
  • @aerokite Check the addition about the example methods. Each method requires a single `nil` check regardless of the complexity of the data structure. – icza Jan 28 '18 at 07:26
  • Seems like there is no easy way. Need redundant code for this checking purpose. – Shahriar Jan 28 '18 at 07:47
  • @aerokite You have to check for `nil` for each pointer at least _once_, _somewhere_. _That_ you cannot omit. All you have to decide is where to put this check. My examples contain `nil` pointer checks for each pointer only once. So there is no redundancy in that. – icza Jan 28 '18 at 07:49
  • I did the same thing in my question, right? Logically – Shahriar Jan 28 '18 at 07:54
  • Thank you @icza for your valuable time. Its helps – Shahriar Jan 28 '18 at 07:59
0

I can use panic recovery method. This will solve my problem. But this seems hacky to me.

func printValue(o *outter) (string, bool) {
    defer func() (string, bool) {
        if r := recover(); r != nil {
            return "", false
        }
        return "", false
    }()
    return o.value.value.value, true
}
Shahriar
  • 13,460
  • 8
  • 78
  • 95
0

There is no easy way. You may go recover way but it's not idiomatic IMO and you should check that you don't catch other unrelated errors.

I prefer a single if instead of multiple. I don't think of the code below ugly or verbose.

func printValue(o *outter) (string, bool) {
    if o.value != nil and o.value.value != nil and o.value.value.value != nil {
        return *o.value.value.value, true
    }
    return "", false
}
Arman Ordookhani
  • 6,031
  • 28
  • 41
  • 1
    You are right. But if nested length is 10, I need lots of checking. I was looking for alternative. – Shahriar Jan 28 '18 at 07:21
0

Using reflection

func getFieldByName(v interface{}, fields string, sep string) interface{} {
    r := reflect.ValueOf(v)
    s := strings.Split(fields, sep)
    for _, field := range s {
        r = reflect.Indirect(r)
        if r.IsValid() {
            r = reflect.Indirect(r).FieldByName(field)
        } else {
            return nil
        }
    }
    if r.IsValid() {
        return r.Interface()
    }
    return nil
}

Using Panic Recovery

type safeGetFn func() interface{}

func safeGet(f safeGetFn, d interface{}) (ret interface{}) {
    defer func() interface{} {
        if r := recover(); r != nil {
            ret = d
        }
        return ret
    }()
    return f()
}

Example Reflection: https://play.golang.org/p/m0_zQqJm7MY

Example Panic Recovery: https://play.golang.org/p/PNPPBXCvHxJ