Understanding Zero Values in Go

Understanding Zero Values in Go

April 20, 2023

In Go, variables of primitive types such as int, float64, bool, string, and others, can have a “zero value” when they are declared but not initialized. A zero value is a default value that is assigned by the compiler or runtime to a variable of a primitive type when no value is explicitly assigned to it.

The concept of zero values is important because it allows programs to work with variables even if they have not been assigned a value yet. This is useful in situations where a default value is sufficient or when a value will be assigned later in the program.

Zero Values for Primitive Types

Let’s look at some examples of zero values for different primitive types in Go:

  • int: A zero value for an int variable is 0.
  • float64: A zero value for a float64 variable is 0.0.
  • bool: A zero value for a bool variable is false.
  • string: A zero value for a string variable is an empty string "".
// Zero value for int
var x int
fmt.Println(x) // Output: 0

// Zero value for float64
var y float64
fmt.Println(y) // Output: 0

// Zero value for bool
var z bool
fmt.Println(z) // Output: false

// Zero value for string
var s string
fmt.Println(s) // Output: ""

Not All Types Have Zero Values

Variables of type channel, struct, map, slice, array, and interface do not have zero values and will have a nil value instead. In Go, nil is not just a default value but represents the absence of one. This means that using uninitialized variables of these types can cause runtime errors in Go programs. For instance, accessing an uninitialized map variable can lead to a runtime panic. Therefore, it’s important to always initialize variables of these types before using them.

ℹ️
Note that if you have an uninitialized slice of a certain type, like []string, and you use fmt to print it, the output will be []. This is because the fmt package uses reflection to determine the type of the slice and what to print. When the slice is uninitialized, the type is still known, so the fmt package is able to print an empty slice of that type. However, the underlying value of the slice is still nil.

How to Avoid Errors Related to Nil Values

To avoid errors related to uninitialized variables in Go, initialize variables when you declare them, or assign a value to them before using them. This ensures that the variable has a valid value and can be used safely in the program.

Let’s look at an example of how uninitialized variables can cause runtime errors in Go:

// uninitialized map
var m map[string]int

// This will cause a runtime panic
m["foo"] = 1
panic: assignment to entry in nil map
Program exited.

In the example above, we declare a variable m of type map[string]int but we do not initialize it. When we try to assign a value to the key "foo" in the map, Go throws a runtime panic because m is nil and cannot be used in this way. To avoid this error, we can initialize the variable before using it:

// initialized map
m := make(map[string]int)

// This works correctly
m["foo"] = 1

In this example, we use the make function to create a new map and assign it to the variable m. Now we can safely use the m map without causing any runtime errors.

Similarly, an uninitialized slice will have a nil value.

// Uninitialized slice
var names []string
fmt.Println(names) // Output: []

if names == nil {
  fmt.Println("Names is an empty slice")
}

// Append a value to the uninitialized slice
names = append(names, "John")
if names != nil {
  fmt.Println("Names is no longer empty")
  fmt.Println(names) // Output: [John]
}

Embrace the zero value

One of the Go Proverb from Rob Pike advises us to “Make the zero value useful”1. While the zero value for non-primitive types is typically nil, it is not always the case.

Consider the bytes.Buffer type in the Go standard library. Its zero value is an empty buffer, which means that a new bytes.Buffer variable is automatically initialized with an empty buffer when it is declared without any explicit initialization. This is a convenient feature because it allows developers to start using the bytes.Buffer variable immediately without having to worry about initializing it first.

By knowing the zero values of a types such as int, float64, bool, and string, developers can use them in situations where a default value is sufficient or when a value will be assigned later in the program. It is also crucial to understand that variables of other types such as channel, struct, map, slice, array, and interface do not have zero values and will have a nil value instead. Using uninitialized variables of these types can cause runtime errors in Go programs. With this knowledge, you will be able to write more robust and error-free programs in Go.