Demystifying nil: Unveiling the Hidden Concepts of Null Values in Go
Think how hard physics would be if particles could think.
— Murray Gell-Mann
Medium Link: Nil Notions that You Should Know in Golang | by Wesley Wei | Jun, 2024 | Programmer’s Career
1.1 Nil in Go
Understanding the meaning of nil in Go and its common use cases in real-world programming.
1 | var nil Type // Type must be a pointer, channel, function, interface, map, or slice type |
In Go, nil is a predeclared identifier representing the zero value for a pointer, channel, function, interface, map, or slice type.
and nil is used in two significant ways:
- To represent an uninitialized object: As explained above, in Go, nil is the zero value for many types.
- To check for errors: Using nil as a condition is a common way to check for errors in Go. (However, it should be noted that nil is an essential part of the language, and you should be able to understand why it’s said so after reading this article.)
1 | file, err := os.Open("file.txt") |
1.2 Nil’s Characteristics
Exploring some characteristics of nil
Nil is not a type with a default type
First, let’s understand the definition of an identifier:
Identifiers: Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter.
from: The Go Programming Language Specification - The Go Programming Language
In Go, other predeclared identifiers have their default types, such as:
- The predeclared identifiers
true
andfalse
have their default types as the built-in typebool
. - The predeclared identifier
iota
has its default type as the built-in typeint
.
However, nil is not predefined with a default type. We must provide enough information in the code to let the compiler infer the expected type of the nil value.
Here’s an example:
1 | func main() { |
Nil is not a keyword
First, let’s understand the definition of a keyword:
Keywords: Keywords are reserved and may not be used as identifiers.
from The Go Programming Language Specification - The Go Programming Language
Here’s an example:
1 | // Case 1 |
From the example, we can see that nil
is not a keyword in Go, and we can define a variable named nil
(however, it’s not recommended).
We can also confirm this from the official documentation:
- Predeclared identifiers in Go: The Go Programming Language Specification - The Go Programming Language
- Keywords list in Go: The Go Programming Language Specification - The Go Programming Language
1.3 Comparison of nil
How nil behaves in comparison operators, and how to compare nil correctly.
Comparison of nil values of same types
- When an uninitialized identifier (predeclared identifier
nil
) does not have a default type, it can be compared with any type of variable. (This can be observed in the case oferr != nil
.)
1 | var p *int |
- When the compiler can infer the expected type of
nil
, partial comparison is possible, but partial comparison is not possible in some cases.
1 | // Case 1 |
In this case, pointers, channels, and interface types can be compared with each other, but function types, map types, and slice types can only be compared with nil
. Comparing two types with each other is not allowed.
In the The Go Programming Language Specification - The Go Programming Language, it is mentioned that “Slice, map, and function types are not comparable.” But why is this the case?
The reason is primarily due to the following points:
- Slices: Slices are dynamic, ordered collections of elements. Their equality depends on the equality of all their elements, in the same order. Since elements themselves can be slices, maps, or functions, the comparison process could become infinitely recursive, leading to potential stack overflows or unpredictable behavior.
- Maps: Maps are unordered collections that associate unique keys with values. As keys themselves, maps would introduce circular references during comparison. Imagine comparing two maps, where one map’s key might reference the other map, creating an endless loop.
- Functions: Functions are essentially blocks of code that can be assigned to variables or passed around. Their equality is a complex concept. Should function equality depend solely on the function’s name, or should it delve into the function’s body and analyze its logic for exact duplication? This ambiguity makes defining a clear comparison for functions impractical.
Golang’s decision to restrict the comparability of slices, maps, and functions promotes efficient map operations and avoids potential issues with circular references or ambiguous function comparisons.
Comparison of nil values of different types
In Go, comparing different types of nil values is generally not allowed, even though they both have a memory representation of zero, as this will result in a compile-time error. The primary reason for this is that the memory layouts of different types are usually not the same. For example:
1 | var p *struct{} = nil |
Additionally, Go itself imposes certain constraints that make comparison between different types of nil values invalid. For instance:
1 | func main() { |
Here, the constraints are already provided, and the reasons for them have been explained in the previous text. The reason for the constraint that a slice or map can only be compared to nil is also given.
Next, we will move on to our main focus, which is examples of different types of nil that can be compared to each other:
1 | func main() { |
Through this example, we can observe that pointer-type nil and channel-type nil can be compared to interface-type nil. Why is this?
Firstly, we need to understand the source code definition of interfaces in Go. An interface-type variable (whether it’s iface
or eface
) consists of two parts: the type and the value.
1 | type iface struct { |
iface
andeface
are both underlying structures used to describe interfaces in Go, with the difference being thatiface
describes an interface with methods, whileeface
describes an empty interface:interface{}
.
It can accept any type of value. When an interface-type variable is not assigned a value (i.e., its type and value are both nil), or when its value is explicitly set to nil, we say that the interface’s value is nil.
When you try to compare fmt.Println(ptr == inter)
, Go compares the type (*int64
) and value (nil
) of ptr
with the type and value (both nil
) of inter
.
Although both ptr
and inter
have a value of nil
, they have different types, so the result of the comparison is false
. Mechanically speaking, Go handles it this way to maintain type safety and avoid any potential unexpected situations.
1.4 Nil in Go: Points to Remember During Usage
Based on the previous discussion, we can draw a preliminary conclusion that in Go, nil
is not just a representation of zero or nothing, but rather has significant meaning and utility in various contexts.
In the following, we will explore some noteworthy points regarding the usage of nil
in Go for different types.
Interfaces and nil
1 | package main |
The output of this program is false
, which may be surprising if you’re expecting true
. This is because for an interface to be nil
, both the type and the value must be nil
. In this case, the type is still animal
, so the comparison with nil
returns false
.
Functions and nil
1 | package main |
From the output, we can learn the following point:
- An instance of a nil pointer can still access its methods, but you cannot access the nil pointer itself (this is a common low-level error that is easy to make when you are a rookie).
Slices, Maps and nil
1 | func main() { |
From the output, we can learn the following points:
- Accessing a
nil
slice will cause apanic
- A
nil
map can be read from, but it cannot be written to, or it will cause apanic
In short, you can simply remember “don’t access nil
pointers”.
Channels and nil
1 | func main() { |
From the output, we can learn the following point:
- Closing a
nil
channel will cause apanic
1.5 More
I recommend watching this video to gain a new understanding of nil
.
GopherCon 2016: Francesc Campoy - Understanding nil - YouTube
generate by AI :
This video is a deep dive into the ‘nil’ concept in the Go programming language. The speaker provides a detailed explanation on the understanding and usage of ‘nil’ in Go programming, emphasizing the importance of ‘nil’ in programming. Here are some main points from the video:
- ‘Nil’ is often seen as an error case in Go, but it should really be considered an important part of the language.
- ‘Nil’ is ubiquitous in the basic structures of Go, such as slices, maps, channels, and functions.
- ‘Nil’ interfaces can represent default behaviors, such as the default operations of the HTTP package.
- ‘Nil’ interfaces can also represent a condition with no errors.
- ‘Nil’ is necessary in concurrent programming.
- The speaker hopes the audience will stop seeing ‘nil’ as an error case and instead understand and embrace it as an essential element of Go.
In essence, the video advocates the recognition and understanding of the importance of ‘nil’, knowing how it’s used in Go, and realizing that avoiding ‘nil’ might not be the best strategy.
1.6 Reference
Go中的nil -Go语言101
GopherCon 2016: Francesc Campoy - Understanding nil - YouTube
中文文章: https://programmerscareer.com/zh-cn/golang-nil/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: If you choose to repost or use this article, please cite the original source.
Comments