Nil and zero value in Golang
It is well known in golang that when allocating storage for a new variable and no explicit initialization is provided, the variable is given to a default value, either zero value for primitive types or nil for nullable types such as pointers, functions, maps etc. More details can be found in go sepc. Some of the nullable types can cause panic when accessing them without initialization. Some examples are as follows.
Access a nil pointer
1 | var p *int |
Assignment to entry in nil map
1 | var m map[string]string |
Access to a nil interface
1 | var p error |
Golang developers have to pay attention to check whether a nullable structure has been initialized before using it. This has been discussed in many articles, such as Go: the Good, the Bad and the Ugly and Nillability and zero-values in go. Since this check is a lot of burden to developers, is there any way to check the potential risk, such as compile time check or static analysis tool.
Try to check nil pointer before panic
Compiler checkers
In Go1 the compiler can’t detect nil pointer since it is legal to pass a nil variable when a pointer, interface, function or anything nullable types is needed. This article proposes a solution to check nil pointer during compile time by borrowing a pointer type that can never be nil, which is like the idea used in Rust and C++. Code sample is as follows.
1 | type myNumber struct { |
Static analysis tools
golangci-lint is a Go linters aggregator, it includes many linters, some useful static analysis tools are also integrated, is it possible to find the nil pointer problem via it? Let’s run linters with the following code
1 | package main |
From the result of lint, we can see only one possible nil variable access is detected. What’s more, the SA5000
detector is only concerned with local variables that don’t escape, global variables or function call with nil map access also doesn’t work. The static check logic can be found in source code of staticcheck tool.
1 | ➜ ./golangci-lint-1.36.0-linux-amd64/golangci-lint run niltype.go |
1 | ➜ go run niltype.go |
As far as I know there doesn’t exist a tool that can detect nil pointer risk with every single nullable type just by static analysis. The best way to avoid nil pointer panic is to check the nullable type is not nil before using it.
Be careful when checking nil interface
Interface in Go contains both type and value, when checking whether nil with an interface, there are two cases
- Interface has nullable type(like pointer, map, etc) and value is nil
- Interface itself has a nil type.
Let’s use the following code as an example to explain the above two cases.
1 | package main |
1 | ➜ go run nil_check.go |
- When passing a nil pointer of
MyError
to functioncheck
, theerr
contains type*MyError
and nil value. - When passing a nil to function
check
directly, theerr
is a nil type itself.
So when we check the nil
of an interface passed from some other place, either returned by a function or passed in from the function parameter, we must be aware there exist two cases. If we don’t want to check whether it is a nil type or only nil value, we can put the check logic before golang type inference, in the above example, we can check whether it is a nil *MyError
type before calling function check
.
Besides, if we have to check the nil of an interface, the article Go: Check Nil interface the right way has a detailed introduction about it.
Summary
To summarize, in every case that we don’t want a nil structure, we must check it manually, there is no easy way to detect nil pointers automatically in golang, and we must be carefull when checking nil with an interface because ther interface could be nil type or not-nil type but nil value.