Before this:Values, variables & types
Pointers
Key takeaways
A pointer holds the address of a value. &x takes the address; *p
dereferences it to reach the value. Go has pointers but no pointer
arithmetic, so you get sharing and mutation without the classic memory bugs. Use
them to let a function modify its caller’s value or to avoid copying a big
struct. A pointer to nothing is nil.
Go passes everything by value — a function gets its own copy of each argument. This lesson explains the escape hatch: pointers, which let a function reach back and change the original.
What is a pointer?
A pointer is a value whose contents are the memory address of another value. Two operators do all the work:
snr := 12.5
p := &snr // p is a *float64 — the address of snr
fmt.Println(*p) // 12.5 — dereference to read the value
*p = 9.0 // write through the pointer
fmt.Println(snr) // 9 — snr itself changed
& means “address of”; * in front of a pointer means “the value it points at.”
*float64 is read as “pointer to float64.”
Why do pointers matter? Modifying the caller
Because arguments are copied, a plain function can’t change what the caller passed:
func reset(x int) { x = 0 } // changes only the copy
func clear(p *int) { *p = 0 } // changes the caller's value
n := 5
reset(n); fmt.Println(n) // 5 — unchanged
clear(&n); fmt.Println(n) // 0 — cleared through the pointer
This is why methods that mutate a struct use a pointer receiver — the subject of Structs & methods.
Avoiding copies of big structs
A Receiver in GopherTrunk might hold filter state, buffers, and AGC settings.
Passing it by value copies all of that on every call. Passing *Receiver copies only
the address:
func (r *Receiver) Step(sample complex64) {
r.agc.Apply(&sample) // mutates r's state in place
}
Rule of thumb: small, read-only values go by value; things you must mutate, or large structs, go by pointer.
The nil pointer
A pointer that points at nothing is nil — the zero value for any pointer type.
Dereferencing a nil pointer panics, so guard it when it might be unset:
if cfg.Filter != nil {
cfg.Filter.Apply(buf)
}
Go has no pointer arithmetic, so a pointer is always either nil or a valid address
of a real value — never a hand-computed offset into memory. That is what makes Go
pointers safe to use everywhere.
Quick check: what does the * operator do in front of a pointer?
Recap
- A pointer holds a value’s address;
&takes it,*dereferences it. - Go has no pointer arithmetic — a pointer is always
nilor a valid address. - Use pointers to modify the caller’s value or to avoid copying big structs.
- The zero pointer is
nil; dereferencingnilpanics, so guard it.
Next up: grouping data with structs and attaching behaviour with methods.
Frequently asked questions
Does Go have pointer arithmetic like C?
No. You can take a pointer with & and follow it with *, but you cannot add to or subtract from an address. That single restriction removes an entire class of memory-corruption bugs while keeping the useful part of pointers: sharing and mutating a value.
When should a function take a pointer instead of a value?
Take a pointer when the function must modify the caller’s value, or when the value is a large struct you don’t want to copy on every call. For small values that only get read, passing by value is simpler and just as fast.