Pointers
A pointer is a variable whose value is the memory address of another variable. Go has pointers, but makes them easy and safe: no arithmetic, no manual allocation (the garbage collector handles that), and automatic dereferencing through struct fields. You'll use them mostly to share state across function boundaries.
Learning objectives
- Explain what a pointer stores.
- Use
&to take an address and*to dereference. - Understand why Go is pass-by-value, and how pointers give you indirection when you need it.
- Know when to prefer a pointer vs a value.
- Check for
nilbefore dereferencing.
What's a pointer?
x := 42
p := &x // p is a *int pointing to x
fmt.Println(p) // e.g. 0xc0000140b8 (an address)
fmt.Println(*p) // 42, the value at that address
&x means "address of x". *p means
"value at the address stored in p". That's the whole mechanism.
Declaring and using
var p *int // nil, doesn't point anywhere yet
x := 10
p = &x
*p = 20 // write through the pointer
fmt.Println(x) // 20: x was modified
Pass-by-value, and why you'd want a pointer
Go always passes arguments by value. A function gets its own copy. So this doesn't do what it looks like:
func setToZero(x int) { x = 0 } // modifies the copy
n := 5
setToZero(n)
fmt.Println(n) // 5, unchanged
Pass a pointer to mutate the original:
func setToZero(x *int) { *x = 0 }
n := 5
setToZero(&n)
fmt.Println(n) // 0
Slices, maps, and channels look like they're passed by reference, but
really Go passes a copy of their header. Since the header
points to shared storage, modifying elements through the copy still
affects the original, that's why modifying s[0] in a
callee shows up in the caller.
Nil and safety
var p *int
fmt.Println(p == nil) // true
fmt.Println(*p) // PANIC: runtime error: invalid memory address
Dereferencing a nil pointer panics. Always guard:
if p != nil { fmt.Println(*p) }
new(T) vs &T{}
// new(T) allocates a zero-valued T and returns a *T
p := new(int) // *int pointing at 0
*p = 42
// &T{...} is more common, literal syntax, often with fields set
type Point struct { X, Y int }
q := &Point{X: 1, Y: 2}
Idiomatically: use &T{...} for structs (or
make for slice/map/channel), new only when
you truly want a zero-valued T and no extra initialization.
Auto-dereference on struct fields and methods
You don't need (*p).X. Go auto-dereferences:
type Point struct { X, Y int }
p := &Point{1, 2}
fmt.Println(p.X) // 1, not (*p).X
p.X = 10 // writes through the pointer
When to use a pointer
- You need the callee to modify the caller's variable.
- The struct is large and you want to avoid copying it on every call.
- You need a sentinel absence value (
nil) to distinguish "no value" from "zero value". - You're implementing a method that needs to modify the receiver (Chapter 20).
Reasons not to use a pointer:
- Small values (int, bool, small structs), copying is cheaper than indirection.
- Immutable data: pointers confuse the reader.
- Slices, maps, channels: they're already reference-like.
No pointer arithmetic
You can't do p + 1 in Go. This is a deliberate restriction
that prevents a huge class of memory-safety bugs. If you need raw byte
manipulation, there's the (unsafe) unsafe.Pointer
, but you'll never need it in normal application code.
Check your understanding
Practice exercises
Swap two values
Write swap(a, b *int) that swaps the values a and b point to. Verify it works.
Show solution
func swap(a, b *int) { *a, *b = *b, *a }
func main() {
x, y := 1, 2
swap(&x, &y)
fmt.Println(x, y) // 2 1
}
Go also supports the swap idiom without pointers for local variables: x, y = y, x. But across a function boundary, you need pointers.
Optional field
Design a struct where MiddleName is optional, use *string. Write a FullName(u User) string function that includes the middle name only if it's non-nil.
Show solution
type User struct {
First, Last string
Middle *string // nil means "not provided"
}
func FullName(u User) string {
if u.Middle != nil {
return fmt.Sprintf("%s %s %s", u.First, *u.Middle, u.Last)
}
return fmt.Sprintf("%s %s", u.First, u.Last)
}
Using *string vs string gives you a way to distinguish "field not set" from "field set to empty string", important for JSON (Chapter 29) and databases (Chapter 30).
Further reading
Indirection under control.