Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Test for nil values in nested stucts

I have a deeply nested struct in go. These are constructed by a json unmarshaller.

Quite some fields in this struct are however 'omitifempty' so I end op with a struct that can have nills in various places.

Example (the real thing is even deeper nested, and big: 400 lines of structs):

package main

import "fmt"

type Foo struct {
    Foo string
    Bar *Bar
}

type Bar struct {
    Bar string
    Baz *Baz
}

type Baz struct {
    Baz string
}

func main() {
    f1 := Foo{Foo: "f1"}
    f2 := Foo{Foo: "f2", Bar: &Bar{Bar: "br2"}}
    f3 := Foo{Foo: "f3", Bar: &Bar{Bar: "br3", Baz: &Baz{Baz: "bz3"}}}

    fmt.Println(f3.Bar.Baz.Baz) //-> bz3
    fmt.Println(f2.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference
    fmt.Println(f1.Bar.Baz.Baz) //-> panic: runtime error: invalid memory address or nil pointer dereference    
    //so far so good, but

    //is there a more generic way to do this kind of testing?
    if f2.Bar != nil && f2.Bar.Baz != nil {
        fmt.Println(f2.Bar.Baz.Baz)
    } else {
        fmt.Println("something nil")
    }
}

The question is if there is a more generic way to test if some node in the reference tree is nil? I need to get a lot of different items and it will be a pain to write all these if statements. Oh and speed is of concern.

like image 233
RickyA Avatar asked Jan 29 '15 12:01

RickyA


2 Answers

One elegant way (in my opinion) of handling it is to add getters to structs that are used as pointers. This "technique" is also used by the generated Go code of protobuf, and it allows natural chaining of method calls without having to worry about runtime panic due to nil pointers.

In your example the Bar and Baz structs are used as pointers, so arm them with getters. The focus is on adding methods with pointer receiver, and first it must be checked if the receiver is nil. If so, return the zero value of the result type. If not, proceed to return the field of the struct:

func (b *Bar) GetBaz() *Baz {
    if b == nil {
        return nil
    }
    return b.Baz
}

func (b *Baz) GetBaz() string {
    if b == nil {
        return ""
    }
    return b.Baz
}

The good thing about methods with pointer receivers is that you may call them with nil receivers. It does not cause a runtime panic until you try to refer to their fields, which we don't, that's why we first check if the receiver is nil (ultimately, receivers act as normal parameters–and it's never an error to pass nil as a pointer argument).

Having the above getters, use is simplified to this, and no runtime panic occurs in any of these examples:

fmt.Println(f3.Bar.GetBaz().GetBaz()) // naturally no panic
fmt.Println(f2.Bar.GetBaz().GetBaz()) // No panic
fmt.Println(f1.Bar.GetBaz().GetBaz()) // No panic

if baz := f2.Bar.GetBaz(); baz != nil {
    fmt.Println(baz.GetBaz())
} else {
    fmt.Println("something nil")
}

Try it on the Go Playground.

like image 179
icza Avatar answered Nov 13 '22 03:11

icza


If you want to avoid 'reflect' (reflection, as a "generic" way to test fields of any struct, a bit as in "Get pointer to value using reflection" or in this gist: it is slower), the surest way would be to implement methods on Foo in order to return the right value

func (foo *Foo) BarBaz() string {
    if f2.Bar != nil && f2.Bar.Baz != nil {
        return f2.Bar.Baz.Baz
    } else {
        fmt.Println("something nil")
        return "" // for example
    } 
}

If there are a lot of such functions to write, maybe go 1.4 go generate command can help generate most of them.

like image 3
VonC Avatar answered Nov 13 '22 04:11

VonC