Golang Zero Values FTW

Keep Calm and Adjust Your Methods

Recently the performance obsessed part of me insisted I benchmark initializing an otherwise nil zero value for a map[string]interface{} inside of a Set(key string, val interface{}) for yet-another-concurrency-safe-map implementation that I’m using. The dangling if statement made me crazy because it only is needed on the first call to Set() but is evaluated for every single call wastefully. I had to know how much wasted performance I was actually incurring.

1.2 ns ain’t so bad

That’s a 1.2 ns penalty that is 1.8% of the total cost of the mutex-protected operation. So the hit for the mutex makes the extra super negligible.

Still, I wish there existed essentially an init() built into the new() functionality that allowed initializing of special zero values automatically for the thing when first used. That would allow the advantage of simplified embedded composition and method delegation for structs that have properties such as the ever-popular map[string]interface{} that are not just nil. What if something like this existed:

func (t *Thing) Init() {
t.d = map[string]interface{}{}
}

Perhaps it could have two versions, a public and a private, both that are automatically called, but mutually exclusive.

func (t *Thing) init() {
t.d = map[string]interface{}{}
}

This is what a lot of people create anyway, but it has to be called forcing the creation and use of a NewThing() constructor for everyone using it, and anyone using that thing that also uses it and so on.

It must have been a tough decision to decide the zero value of a map. One could argue that the better zero value should have been an empty map instead of nil as with the empty string value. I understand that a nil makes it more explicit to everyone that such a thing is essentially just another struct that happens to be built in, which maps/hashes/arrays really are at the lowest level.

On the other hand, perhaps all the magic method delegation and zero-value initialization that happens with these embeds is the devil. Maybe everyone should be using constructors instead?

Constructors provide the obvious benefit of allowing other initialization to be added later. When a developer decides to make an embeddable struct—and encourage such use, as with sync.Mutex—there is a solid commitment to never, ever adding anything to that thing that needs any initialization at all. Adding a constructor later because suddenly it is needed essentially becomes a new thing and complicates any API using the original thing.

Perhaps this balance of right-choice-for-the-job needs to be simply left where it is. Either are available and have purposes. One is not more right than the other, but understanding the performance implications is important. More important, however, is the level of expectation you are building into your API from the beginning.

My conclusion? Like so many other core libraries, create a constructor unless you have a rock-solid case not to do so, but don’t be overly afraid of embeddable constructor-less structs and their new() zero values.