Nil pointers
The zero value of any pointer type is nil. An uninitialised pointer points at nothing. Dereferencing it is a runtime panic, not a silent success. This lesson covers what nil pointers look like, when they are genuinely useful, and how to avoid the crash.
Pointers start out as nil
Any *T variable you declare without initialising is nil:
var p *int
fmt.Println(p) // <nil>
fmt.Println(p == nil) // true
You can compare a pointer to nil with == and !=. You can pass a nil pointer to functions, store one in maps and slices, and put one inside a struct field. None of those operations are a problem. The problem only shows up when you try to follow the pointer to a value that does not exist.
Dereferencing nil is a panic
Put this in the playground and watch the program crash:
var p *int
fmt.Println(*p)
Output:
panic: runtime error: invalid memory address or nil pointer dereference
The panic is loud and ends the program (unless you catch it with recover, covered in the Errors chapter). There is no nil-safe dereference operator like ?. in some other languages. *p fails on a nil p, full stop.
Guard with if p != nil
The usual shape for a function that takes a pointer-to-something-maybe-missing is to check first and dereference second:
func greet(name *string) {
if name == nil {
fmt.Println("Hello, stranger")
return
}
fmt.Println("Hello,", *name)
}
func main() {
greet(nil)
n := "Kamran"
greet(&n)
}
Output:
Hello, stranger
Hello, Kamran
greet checks for nil before dereferencing. Every Go function that accepts an optional pointer parameter is shaped the same way.
When nil pointers are genuinely useful
Nil is how Go represents "this value is not set". Because int has a zero value of 0 and string has a zero value of "", you cannot tell "absent" from "present with zero" using the plain types alone. A *int can make the distinction: nil means absent; a non-nil pointer means present with some specific value, even if that value happens to be zero.
This shows up in JSON parsing (a missing optional number becomes a nil *int, covered in the Standard library chapter), in configuration that has optional fields, and anywhere else the difference between "zero" and "not provided" matters. The comma-ok form you saw with maps is another way to represent the same idea; pointers give you a third, for single values outside a map.
The most common first-time Go crash is dereferencing a pointer you forgot to check. Before every *p in a function body, ask yourself: could p be nil here? If the answer is "yes" or "maybe", add an if p == nil guard. The compiler will not save you.
If your function accepts a *T parameter, the caller can pass nil and you have to plan for it. Either guard explicitly at the top of the function, or document that the parameter is required and let the caller crash on violation. Whichever you pick, be consistent across your codebase: a reader should not have to guess which functions tolerate nil and which do not.
The starter has an inc function that takes a *int and increments the value it points at. It does not currently guard against nil, so calling inc(nil) crashes.
Fix inc so that:
- If the pointer is nil, it does nothing (simply returns without dereferencing).
- Otherwise, it increments
*pby one.
The starter's main calls inc(nil) first, then declares x := 41 and calls inc(&x), then prints x. With the fix in place, the first call is a no-op (no panic) and the second leaves x at 42.
Expected output:
42
42