What nil Really Means in Go

What nil Really Means in Go

Zero Values for Most Types

Besides numeric types defaulting to 0, booleans to false, and strings to empty strings, all other built-in types like pointers, slices, maps, channels, functions, and even interfaces have nil as their zero value.

Just an Identifier, Not a Keyword

Here’s the interesting part: nil means different things in different types.

In Pointers

nil represents a null pointer, meaning no address is referenced.

In Slices

Slices are essentially structs like this:

type mySlice struct {
	ptr *elem // actual data
	len int // number of elements
	cap int	// capacity
}

Their nil value is really the struct’s zero value: mySlice{nil, 0, 0}.

In Channels, Maps, Functions

Their nil values are just null pointers (uninitialized). When not nil, they point to specific implementation objects returned by initialization.

In Interfaces

This gets trickier. An interface is essentially a struct with two fields: Type and Value.

type _interface struct {
	dynamicTypeInfo *_implementation
	dynamicValue    unsafe.Pointer // generic pointer type
}

type _implementation struct {
	itype   *_type   // interface type
	dtype   *_type   // runtime type, must implement itype
	methods []*_func // function pointers implementing itype methods
}

type _func struct {
	name      string  
	methodSig uint // two functions with same signature share method ID
	funcSig   uint // receiver parameter ID

	// other info...
}

So an interface’s nil value, when not assigned, is struct{nil, nil}.

In Go error handling, we often see code like if err != nil. Error is also an interface. If we return a nil pointer of a custom error type like ptr, it gets wrapped into the error interface type as a struct struct{p, nil}. All nil equality checks will return false because p is the dynamic type _implementation pointer. This tells us not to return concrete error types, even if that custom struct value is nil.

The Real Nil Situation Needs Analysis

Sometimes nil isn’t what we think it is. For example, null pointer nil and empty interface nil are not equal at all.

Null Pointer Receivers

In C++, if an object pointer is null, we can’t call its member functions. But in Go, we can check if the receiver pointer is nil and take different actions in the function.

Nil Slices

When appending to a nil slice, the runtime allocates space for the slice’s null pointer.

Nil Maps

A nil map value is empty and read-only. Instead of passing an empty map literal declaration as an argument, we can pass nil.

Nil Channels

When an initialized channel closes, continuing to receive from it returns zero values. So we should check if the channel remains open during reception. If the channel has closed, we assign the receiving channel to nil, making select skip directly and avoiding repeated channel contention for CPU operations in loop selects across multiple channels.

Nil Functions

Null functions can set default functions or user-provided custom functions.

Nil Interfaces

Nil interfaces can represent using default operations.

Other Notes

struct{} takes 0 bytes of storage space. All zero-sized values have address 0x1beeb0. If we take addresses of two empty structs, we’ll find they have the same address.