Golangs Mistake

Apr. 5, 2023

Golang is not a perfect language. It has multiple flaws, that others have pointed out.

But I still use it every day because it’s familiar, easy to write, fast and most importantly lets me get things done. But there is one thing that makes this languge a pain to use. It’s zero values.

Zero values

Most languages contain The Billion Dollar Mistake. Everyone has at some point forgotten to check for null before using some variable and then been forced to watch as the spaghetti mess explodes right in front of your eyes.

The Go language has thought about this and came up with so-called zero values. It all boils down to one sentence. Variables declared without an explicit initial value are given their zero value.

Sounds good, now I don’t have to worry about values that I didn’t give initial value. And everyone now can sleep in peace knowing there are gonna be no more dreaded null pointer exceptions.

WRONG!

Struct example

This opens another can of worms where you now have so many other small problems that bite you in the ass in such a way that you can’t imagine it.

The most simple example. You have a struct that looks something like this

type Foo struct {
    Baz int
}

You use this tiny little struct all over your code base doing for Foo things

func main() {
    f := Foo{Baz: 1}
    someFunc(f)
}

But then you remember the forgotten friend that was supposed to be part of your struct called Bar so you add it.

type Foo struct {
    Baz int
    Bar int
}

And let’s imagine that Bar is a very important part of Foo and needs to be used everywhere. But here comes the problem. Now you have to go to EVERY SINGLE PLACE where Foo is used and add the correct value there. And You better not forget one, because Go will do its magic and just silently set Bar to 0 in every place where you have not used it, and it won’t complain or tell anything about that to you.

One might say: “Oh but you should have just used a constructor function”, or “builder pattern” or whatever else you might think of.

It still doesn’t fix the problems of someone (that being me) forgetting to use the constructor/builder functions, because Go doesn’t have a way to disallow that.

JSON

Then there is the issue of using JSON or any other data format that has null values. Lord have mercy on your soul and fingers(although since AI writes everything nowadays, that might not be such a problem) if any of your JSON booleans have a different meaning when they are null or false/true.

You see if you try to unmarshal the following JSON string

{
  "is_zero_value_good": null
}

Into a struct that looks like

type Foo struct {
    IsZeroValueGood bool `json:"is_zero_value_good"`
}

This will come out as

Foo{
    IsZeroValueGood: false
}

And if you want to have IsZeroValueGood equal nil You have to declare the value as a pointer. And that kinda defeats the whole purpose of zero values.

And it also makes the whole thing a mess to write, because now you have to do some magic when initializing the struct like this

type Foo struct {
	IsZeroValueGood bool `json:"is_zero_value_good"`
}
falseValue := false
f := Foo{
	IsZeroValueGood: &falseValue,
}

Or create helper functions like every other library has, where they wrap the basic types, and return a pointer to the value. For example the GitLab API library

Or if you are very fancy and can use Generics you can create the greatest helper of them all.

func Ptr[T any](v T) *T {
	p := new(T)
	*p = v
	return p
}

And this whole mess has me replying to issues about “Why does this don’t work” in the popular validation library Validator.

And the fact that so many people have problems with this is not the programmer’s fault, but the language’s.

How to fix it?

Simple: Be like Rust and use Options, or at least drop the Zero value concept and disallow initializing structs without providing a value. Even Typescript has this right, and if Typescript does something better than you, it means you are doing something wrong.