The nil pointer and new
What happens if you take a slip for an address but write nothing on it? There is nowhere to go. In Go that is a pointer with no address. A pointer declared with var does not point anywhere yet. A program that writes through it compiles, but stops with a panic when it runs:
package main
func main() {
var p *int
*p = 5 // ✗
}panic: runtime error: invalid memory address or nil pointer dereferenceThe first line of the panic names the cause: an invalid memory address while dereferencing a nil pointer. Why didn't the compiler catch it?
- It checks types, and
*phas the right type,int. - That the slip is blank only turns out while the program runs.
This panic is one of the most common ones in real Go programs.
The nil value
The zero value of any pointer is nil—that blank slip. You can print it and compare it with nil:
var p *int
fmt.Println(p, p == nil) // <nil> trueWhat you can and cannot do with a nil pointer:
- you can print it and compare it with
nil, as in the example above; - you cannot dereference it: reading
*pcauses the same panic as writing; - you cannot read a
0from there either: there is no house behindnil.
This is where a pointer differs from a map: a missing map key reads as the zero value, but a nil pointer does not.
A check before dereferencing
If a pointer may be nil, check it before dereferencing: if p != nil { fmt.Println(*p) }. A function with a pointer parameter needs this check too: the caller may have handed it a blank slip.
Two ways to get an address
For a pointer not to be nil, it needs an address. There are two ways to get one.
The address of an existing variable
The & operator takes the address of a variable that already exists: p := &x. The slip leads to the house named x, so you can reach it both by name and by the slip. A write *p = 5 shows through x.
The new function
You can ask Go to build a new house. The built-in function new does that: it creates a new variable of the given type and returns its address. The variable gets the zero value of its type:
p := new(int)
fmt.Println(*p) // 0
*p = 5
fmt.Println(*p, p != nil) // 5 trueLet's see what happened here:
new(int)built a house, put0in it and handed back a slip with its address.- So the first
Printlnprints0. *p = 5puts5into the house, and the secondPrintlnprints5 true.
p := new(int) works like the pair of lines x := 0 and p := &x. There is one difference: the new house has no name of its own, and you can reach it only through the slip p. So new is handy when you need a pointer but no separate name for the variable.
Each call to new builds a separate house. After a := new(int) and b := new(int), the comparison a == b is false, while *a == *b is true. The variables have different addresses but equal values.
new and make
Interviewers like to ask how new differs from make. The answer has two parts:
makecreates slices and maps and returns a ready value, not a pointer;newworks for any type and always returns a pointer.
Common mistake
A pointer was checked for nil through a dereference:
p := new(int)
if *p == nil { // ✗
fmt.Println("no value")
}invalid operation: *p == nil (mismatched types int and untyped nil)The program does not compile: *p is a number of type int, and a number is never nil. A slip can be blank, a house cannot, so you compare the pointer itself with nil: p == nil.
Key points
- The zero value of a pointer is
nil: a pointer with no address. - Reading or writing through a
nilpointer compiles, but panics when the program runs. new(T)creates a variable of typeTwith its zero value and returns its address;makecreates slices and maps.- Compare the pointer with
nil(p == nil), not*p.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.