DEV Community

Hamp Goodwin
Hamp Goodwin

Posted on

Use Go Zero Values

In this article I make an argument for using the Zero Value in Go over the Nil value, where appropriate.


🔥 Hot Take 🔥

  • There is only one situation in which we should use a pointer to scalar type of value nil to mean empty, and that is when the zero value has meaning.
  • Avoid pointer to struct types where not necessary (concurrency safe mutations and non LOB)

Zero Values

Zero values are default values for instantiation of declared but unassigned values. See this tour of go example.

When we instantiate a variable in go we have some options:

var mystring string = "mystring"
mystring := "mystring"
var mystring string
Enter fullscreen mode Exit fullscreen mode

In var mystring string = "mystring" we combine variable declaration with an explicit initalization with value of “mystring”

In mystring := "mystring" we have a short hand inferred type declaration informed by it’s initialization value of “string”

In var mystring string we have type declaration… but no initialization? So what therefore, is the initial value? It is the Zero Value.

Why?

At this point, I was about to go on a lengthy explanation of pointers as counterpoint, but no one wants that. You can instead read this article which has a, in my opinion, fair criticism of the “value not a value”. I believe strongly that is is a bad practice to use different type and value to provide meaning that our types value is empty.

🔥😈 example:

type Base struct {
    ID *string
}
type Other struct {
  Base *Base // y? because ynot.
}

type Composed struct {
    ID string
    Base *Base
    Other *Other
}

func (c Composed) equalIDs() (bool, error) {
    // access some stuff
    var composedBaseID *string
  if c.Base != nil {
        composedBaseID = c.Base.ID
  }
    var composedOtherBaseID *string
    if c.Other != nil {
        if c.Other.Base != nil {
            idIWant = c.Other.Base.ID
    }
    }
  var emptyStrPtr string
    if composedBaseID == &emptyStrPtr || composedOtherBaseID == &emptyStrPtr {
        return false, errors.New("either id is empty because the pointer didn't even take care of that for us")
    }
}

Enter fullscreen mode Exit fullscreen mode

🎵😌 example:

type Base struct {
    ID
}
type Other struct {
  Base Base // y? because ynot.
}

type Composed struct {
    ID string
    Base Base
    Other Other
}

func (c Composed) equalIDs() (bool, error) {
    if c.Base.ID == "" || c.Other.Base.ID == "" {
        return false, errors.New("either ID is empty")
    }
    return c.BaseID == c.Other.Base.ID, nil
}
Enter fullscreen mode Exit fullscreen mode

Yes, these are contrived examples, but very often seen in the wild.

In a language where type composition is powerful and often used, having composed types with pointers requires checking nil value at each value access depth where the pointer exists to check for empty before checking the value. Furthermore, once we access the pointer, if pointer to struct, we still need to do zero value validation in most cases anyways, as seen in the hell example and here.

Top comments (0)