GO nil detection [interface, slice, pointer, struct, map]

GO nil detection [interface, slice, pointer, struct, map]

In the previous post, I already talked about the zero values and nil in Golang. In today’s article, we will discuss how to detect nil value in Go.**nil**is a predefined identifier in Go that represents zero values of many types.nil is usually mistaken as null (or NULL) in other languages, but they are different.


Using nil in Golang

We can usenil without declaring it. nil can represent the zero values of many data types:

  • pointer types (including type-unsafe ones).
  • map types.
  • slice types.
  • function types.
  • channel types.
  • interface types.

Common nil detection in Golang

When storage is allocated for avariable, either through a declaration or a call ofnew, or when a new value is created, either through a composite literal or a call ofmake, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to thezero valuefor its type:falsefor booleans,0for numeric types,""for strings, andnilfor pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

In the example below, we will try to convert a string to unit8 and see how we can catch the error if it is not nil:

package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "111a"
    fmt.Printf("%v %T\n", s, s)
    in, err := strconv.Atoi(s)
    // catch the error if it is not nil
    if err != nil {
        fmt.Println(err) //strconv.Atoi: parsing "111a": invalid syntax
    }
    fmt.Printf("%v %T\n", in, in) // 0 int

    s2 := "111"
    fmt.Printf("%v %T\n", s2, s2)
    in2, err := strconv.Atoi(s2)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%v %T\n", in2, in2) // 0 int
}

Output:

111a string
strconv.Atoi: parsing "111a": invalid syntax
0 int
111 string
111 int

Explanation

In the code shown below, we try to handle errors by creating an error variable which is an error type. The zero value for the error type is nil, so we can use the operator != to compare our variable to nil. If it is not nil, it means our program has some problems, we will print the error message and try to handle this case.

image


Check if a pointer is nil or not

In this example, the pointer is checked whether it is a nil pointer or not:

package main

import "fmt"

type Test struct {
}

func main() {
    var ptr *Test // pointer
    var intVal = 123
    var ptr1 *int = &intVal

    fmt.Printf("ptr is a nil pointer?: %v\n", ptr == nil)
    fmt.Printf("ptr1 is a nil pointer?: %v\n", ptr1 == nil)
}

Output:

ptr is a nil pointer?: true
ptr1 is a nil pointer?: false

Check if a struct is nil or not

In this example, we will try to compare a struct instance to nil.

package main

import "fmt"

type Config struct {
    Host     string
    Port     float64
    Username string
    Password string
    Setup    bool
}

func main() {
    var conf Config
    if conf == nil {
        fmt.Println("The config is nil!")
    } else {
        fmt.Println("The config is not nil")
    }
}

Explanation

You are comparing a structure instance and nil, which is why the compiler prompts an error message. Since they are not the same type, so the comparison is invalid. The elements of an array or struct will have their fields zeroed if no value is specified. So there’s no way to set a struct value to nil. But you could set the value of a pointer to a struct to nil. As mentioned above, the zero value of pointer types is nil.

package main

import "fmt"

type Config struct {
    Host     string
    Port     float64
    Username string
    Password string
    Setup    bool
}

func main() {
    var conf *Config // nil

    // not nil
    conf2 := &Config{
        Host:     "www.golinuxcloud.com",
        Port:     22,
        Username: "testuser",
        Password: "pwd",
    }

    if conf == nil {
        fmt.Println("The config is nil!")
    } else {
        fmt.Println("The config is not nil")
    }

    if conf2 == nil {
        fmt.Println("The config is nil!")
    } else {
        fmt.Println("The config is not nil")
    }
}

Output:

The config is nil!
The config is not nil

Check if an interface is nil or not

In this example, the interface is checked whether it is a nil interface or not.

package main

import (
    "fmt"
)

type TestInterface interface {
    Summary() string
}

type MyString struct {
    Message string
}

// Implementing methods of
func (myStr MyString) Summary() string {
    return "This is a test message" + myStr.Message
}

func main() {
    var interf TestInterface
    fmt.Printf("interf is a nil interface: %v\n", interf == nil)

    var interf2 TestInterface
    interf2 = MyString{"from GoLinuxCloud"}
    fmt.Printf("interf2 is a nil interface: %v\n", interf2 == nil)
}

Output:

interf is a nil interface: true
interf2 is a nil interface: false

Check if a slice is nil or not

In this example, we are going to check if a slice is nil or not. For the slice, you must clearly understand about an empty and a nil slice. nil and empty slices (with 0 capacity) are not the same, but their observable behavior is the same (almost all the time):

  • We can call the builtin len()andcap() functions for both nil and empty slice
  • We can iterate through them with for range(will be 0 iterations)
package main

import "fmt"

func main() {
    var slice1 []int         // nil slice
    slice2 := []int{}        // non-nil, empty slice
    slice3 := make([]int, 0) // non-nil, empty slice
    fmt.Println("slice1", len(slice1), slice1 == nil, slice1[:], slice1[:] == nil)
    fmt.Println("slices2", len(slice2), slice2 == nil, slice2[:], slice2[:] == nil)
    fmt.Println("slice3", len(slice3), slice3 == nil, slice3[:], slice3[:] == nil)
}

Output:

slice1 0 true [] true
slice2 0 false [] false
slice3 0 false [] false

Check if a map is nil or not

The following example shows how to check if a map is empty using the length check and check if the map is nil or not:

package main

import "fmt"

func main() {
    tempMap := make(map[string]string) // empty map
    var tempMap2 map[string]string     // nil map
    if len(tempMap) == 0 {
        fmt.Println("tempMap is empty")
    } else {
        fmt.Println("tempMap is not empty")
    }

    if tempMap2 == nil {
        fmt.Println("tempMap2 is nil")
    } else {
        fmt.Println("tempMap2 is not nil")
    }
}

Output:

tempMap is empty
tempMap2 is nil

Summary

As you can see,nil is not the zero value for every type but only for pointers, functions, interfaces, slices, channels, and maps. For nil detection, we simply use the comparator !=. Besides that, some packages provide the IsZero() function to check if it is nil or not. For example, the time.Time type has the IsZero() which reports whether the variable represents the zero time instant, January 1, year 1, 00:00:00 UTC.


References

https://go.dev/ref/spec#The_zero_value
https://go.dev/doc/tutorial/handle-errors
https://pkg.go.dev/errors

Tuan Nguyen

Tuan Nguyen

Data Scientist

Proficient in Golang, Python, Java, MongoDB, Selenium, Spring Boot, Kubernetes, Scrapy, API development, Docker, Data Scraping, PrimeFaces, Linux, Data Structures, and Data Mining. With expertise spanning these technologies, he develops robust solutions and implements efficient data processing and management strategies across various projects and platforms.