Pointers
What & and * do, when you need them, and why Go pointers cannot hurt you the way C ones can.
- Predict whether a function call changes the caller's value when it takes a value versus a pointer.
- Write a function that changes its argument through a pointer.
- Explain how a nil
*inttells "no value" apart from zero.
What a pointer is
Every value your program holds lives somewhere in memory, and every location has an address. A pointer is a value that holds one of those addresses instead of the data itself. It is a way of saying “the thing over there” rather than carrying a copy of the thing around.
You need them for two reasons. Go copies values when you pass them to a function, so without a pointer a function cannot change what the caller gave it. And copying a large value on every call costs time you may not want to spend.
Pointers have a bad reputation from C, where they can be moved around arithmetically and freed by hand. Go removes both of those, which takes away almost everything that made them dangerous.
The two operators
x := 42p := &x // & takes the ADDRESS. p is of type *int.fmt.Println(p) // 0xc000018030 — an addressfmt.Printf("%p\n", p) // %p is the verb for printing an addressfmt.Println(*p) // 42 — * DEREFERENCES: "the value at this address"*p = 100 // write through the pointerfmt.Println(x) // 100 — x changedvar q *int // the zero value of a pointer is nilfmt.Println(q == nil) // true// fmt.Println(*q) // PANIC: nil pointer dereferenceWhy you would want one
Go passes everything by value, so a function receives a copy. A pointer is how you let a function change the caller’s variable.
func double(n int) { n *= 2 } // changes the copy. Caller sees nothing.func doubleP(n *int) { *n *= 2 } // changes the caller's variable.x := 5double(x)fmt.Println(x) // 5doubleP(&x)fmt.Println(x) // 10When to use a pointer
| Use a pointer | Use a value |
|---|---|
| the function must change the argument | the function only reads it |
| "no value" and zero must be different (see below) | zero is a fine default |
| the value is large and copying is measurable | the value is a number, a bool or a short string |
A pointer as "maybe a value"
Because a pointer can be nil, it can say "there is no value here", which a plain int cannot: an int is always some number, even if it is 0. *int lets nil mean "not given" while 0 still means zero.
func describe(age *int) string { if age == nil { return "age not given" } return fmt.Sprint("age ", *age) // could be 0, and that is a real answer}new versus make
p := new(int) // *int pointing at a zero int. Rarely used.// make is ONLY for slices, maps, and channels — it initialises thems := make([]int, 0, 10)m := make(map[string]int)ch := make(chan int, 5)// new(map[string]int) gives you a POINTER TO A NIL MAP. Writing to it panics.What Go pointers cannot do
- No pointer arithmetic. You cannot do
p + 1to walk through memory, so buffer overruns through a pointer are ruled out. - No dangling pointers. Returning
&xfrom a function is safe, because the garbage collector keepsxalive. In C that returns a pointer into a stack frame that no longer exists; in Go, escape analysis movesxto the heap. - No manual free. The GC handles it. You cannot double-free or use-after-free.
- The main thing that can bite you: a nil dereference, which panics rather than corrupting memory. A crash with a stack trace beats silent corruption.
Try it yourself
package mainimport "fmt"func double(n int) { n *= 2 } // gets a copyfunc doubleP(n *int) { *n *= 2 } // gets the addressfunc describe(age *int) string { if age == nil { return "age not given" } return fmt.Sprint("age ", *age)}func main() { x := 5 double(x) fmt.Println("after value call :", x) doubleP(&x) fmt.Println("after pointer call:", x) var missing *int zero := 0 fmt.Println(describe(missing)) // age not given fmt.Println(describe(&zero)) // age 0}&xtakes an address,*preads and writes through it.- Pointers let a function change the caller’s variable instead of a copy.
- A
nilpointer can mean "no value", which zero cannot. makefor slices/maps/channels. No pointer arithmetic, no manual free.
func swap(a, b *int) that swaps two variables, and func maxOf(nums []int) *int that returns nil for an empty slice. Call both and print the results.- 1Write
double(n *int)and call it. Then writedoubleVal(n int)and confirm the caller sees nothing. - 2Write
safeDiv(a, b int) *intthat returnsnilwhenbis 0 and a pointer toa / botherwise. PrintsafeDiv(0, 5)andsafeDiv(5, 0)and explain the difference. - 3Return
&xfrom a function wherexis a local. Confirm it works, then read why withgo build -gcflags=-m.