Go's nil Is More Complicated Than You Think
Min-jun Kim
Dev Intern · Leapcell

In-depth Analysis of nil in the Go Language
In the practice of Go language programming, the use of nil is extremely common. For example, the default type is assigned as nil, the error return value often uses return nil, and multiple types use if != nil for judgment, etc. However, regarding the knowledge point of nil, developers need to have an in-depth understanding of its essence and related characteristics. This article will comprehensively analyze nil around the following core questions:
- Is
nila keyword, a type, or a variable? - Which types can use the
!= nilsyntax? - What are the similarities and differences in the interaction between different types and
nil? - Why do some composite structures need
make(Type)to be used after defining variables? - Why can a
slicebe used directly after being defined, while amapmust be initialized throughmake(Type)?
I. The Essence and Definition of nil
Essentially, nil is a predeclared variable, and its definition in the official Go source code is as follows:
// nil is a predeclared identifier representing the zero value for a // pointer, channel, func, interface, map, or slice type. var nil Type // Type must be a pointer, channel, func, interface, map, or slice type // Type is here for the purposes of documentation only. It is a stand-in // for any Go type, but represents the same type for any given function // invocation. type Type int
Two key pieces of information can be obtained from the above definition:
nilis a variable of theTypetype, andTypeis defined based onint.nilis only applicable to six types: pointer, function, interface,map,slice, and channel.
II. Similarities and Differences in Variable Definition between Go and C
2.1 Similarities
The essence of defining variables in both Go and C languages is to allocate a specified amount of memory space and bind the variable name.
2.2 Differences
-
Memory Initialization Method:
- Go: The variable memory adopts the zero-allocation strategy, that is, it ensures that the initial values of the allocated memory block are all 0. Variables on the stack are guaranteed to be set to 0 by the compiler during the compilation phase, and variables on the heap are controlled by the
mallocgcfunction of theruntimethrough theneedzeroparameter (this parameter istruewhen the user defines a variable).
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { // ... }- C: By default, only memory is allocated, and the data in it is in an undefined state and may contain random values.
- Go: The variable memory adopts the zero-allocation strategy, that is, it ensures that the initial values of the allocated memory block are all 0. Variables on the stack are guaranteed to be set to 0 by the compiler during the compilation phase, and variables on the heap are controlled by the
-
Performance Considerations: Although Go's zero-allocation is a language feature, some internal processes of the
runtimecan skip this step to improve performance and avoid unnecessary overhead.
III. Understanding the Semantics of nil
- At the Compiler Level:
nilis not just a simple value but a condition that triggers special processing by the compiler. When there are comparison or assignment operations withnilin the code, the compiler will check whether the type belongs to the six supported types and ensure that the memory of the corresponding structure is in a state of all 0s. - Type Restrictions: Only pointer, function, interface,
map,slice, and channel types can be compared or assigned withnil, and other types will report an error during the compilation period.
IV. Analysis of the Six Types Related to nil
4.1 slice Type
Variable Definition and Memory Layout
- Definition Methods:
var slice1 []byte: Only declares the variable. After escape analysis, 24 bytes of memory are allocated on the stack or heap (including 1 pointer field and 2 8-byte integer fields).var slice3 = make([]byte, 0): Calls themakeslicefunction, also allocates 24 bytes, but the initialization logic is different.
- Memory Structure:
type slice struct { array unsafe.Pointer // The starting address of the memory block len int // The actual size used cap int // The total capacity }
Characteristics of nil Operations
- Assignment:
slice = nilsets the 24-byte memory block of the variable to 0. - Judgment: Only checks whether the
arraypointer field is 0 and ignores thelenandcapfields.
4.2 map Type
Variable Definition and Memory Layout
- Definition Methods:
var m1 map[string]int: Only allocates an 8-byte pointer variable.var m2 = make(map[string]int): Calls themakehmapfunction, allocates a completehmapstructure, and assigns the pointer to the variable.
- Memory Structure:
type hmap struct { count int // ... Other fields are omitted buckets unsafe.Pointer // ... }
Characteristics of nil Operations
- Assignment:
map = nilonly sets the pointer variable to 0, and thehmapstructure needs to rely on garbage collection for processing. - Judgment: Checks whether the pointer is a non-0 value.
4.3 interface Type
Variable Definition and Memory Layout
- Structural Types:
iface: A regular interface, containing atabpointer and adatapointer.eface: An empty interface, containing a_typepointer and adatapointer.
- Memory Occupancy: Both structures occupy 16 bytes.
Characteristics of nil Operations
- Assignment:
interface = nilsets the 16-byte memory block to 0. - Judgment: Checks whether the first pointer field is 0.
4.4 channel Type
Variable Definition and Memory Layout
- Definition Methods:
var c1 chan struct{}: Only allocates an 8-byte pointer variable.var c2 = make(chan struct{}): Calls themakechanfunction, creates anhchanstructure, and assigns the pointer.
- Memory Structure: The variable itself is an 8-byte pointer pointing to the
hchanstructure.
Characteristics of nil Operations
- Assignment:
channel = nilsets the pointer to 0. - Judgment: Checks whether the pointer is a non-0 value.
4.5 Pointer Type
- Memory Layout: An 8-byte integer pointer.
nilOperations: Assigningnilsets the pointer to 0, and when judging, checks whether the pointer is 0.
4.6 Function Type
- Memory Layout: An 8-byte function pointer.
nilOperations: Similar to the pointer type, both assignment and judgment are for the 8-byte pointer.
V. Summary
- The Essence of Variables: Variables are named bindings of memory blocks, and the Go language ensures that the variable memory is initialized to 0.
- The Applicable Scope of
nil: Only six types, namely pointer, function, interface,map,slice, and channel, support comparison and assignment withnil. - The Role of
nil: As a condition that triggers special processing by the compiler, it ensures type safety. - Differences in Composite Types:
mapandchannelneed to be initialized withmakebecause defining withvaronly allocates a pointer, and the core management structure needs to be explicitly created.- A
slicecan be used directly after being defined because its core management structure is allocated when it is declared.
- Unification of
nilOperations: For the six types, assigningnilmeans setting the memory of the variable itself to 0, and when judgingnil, it is all based on the first pointer field of the variable.
By having an in-depth understanding of the mechanism of nil, developers can write more robust Go code more accurately and avoid runtime errors caused by improper handling of nil.
Leapcell: The Best of Serverless Web Hosting
Finally, I would like to recommend a platform that is most suitable for deploying Go services: Leapcell

🚀 Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
🌍 Deploy Unlimited Projects for Free
Only pay for what you use—no requests, no charges.
⚡ Pay-as-You-Go, No Hidden Costs
No idle fees, just seamless scalability.

🔹 Follow us on Twitter: @LeapcellHQ

