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.
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!
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.
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.
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.