← thecodex.expert · The Codex Family of Knowledge
Go

Pointers

Go keeps the useful half of C’s pointers — sharing and mutating data — and removes the dangerous half: there is no pointer arithmetic.

Go 1.26 No pointer arithmetic Last verified:
Where this bites you

You model a user whose profile hasn't loaded yet as a nil pointer field — reasonable, since not every user has filled theirs in. A method reads straight through it, u.Profile.Bio, and it works great in every test, because every test user has a profile. Then a real signup with no profile yet hits the same code path in production, and the whole request dies.

Gonilptr.go
type User struct { Profile *Profile }

func (u *User) Bio() string {
    return u.Profile.Bio   // Profile is nil — this panics
}

u := &User{}
fmt.Println(u.Bio())
// panic: runtime error: invalid memory address or nil pointer dereference

The last section on this page names the rule; its last code block is the fix, a nil check before the dereference ever happens.

Canonical Definition

A pointer is a value that holds the memory address of another value. The & operator takes the address of a variable, producing a pointer; the * operator, applied to a pointer, dereferences it to get (or set) the value it points to. Unlike C, Go does not allow arithmetic on pointers (no ptr++), which removes an entire class of memory-corruption bugs while keeping the ability to share and mutate data across function calls.

& and *: address-of and dereference

&x produces a pointer to x, of type *T if x is type T. Given a pointer, *p reads or writes the value it points to.

Gopointers.go
x := 10
p := &x          // p is a *int, holding x's address
fmt.Println(*p)  // 10 — dereference to read the value

*p = 20          // dereference to write — this changes x too
fmt.Println(x)   // 20

Why pass a pointer to a function

Go passes arguments by value — a function normally receives a copy. Passing a pointer instead lets a function mutate the caller's original data, and avoids copying large structs on every call.

Gopointer_args.go
func double(n int) {      // receives a COPY
    n *= 2
}

func doublePtr(n *int) {  // receives the ADDRESS
    *n *= 2
}

x := 5
double(x)
fmt.Println(x)     // still 5 — double() only changed its copy

doublePtr(&x)
fmt.Println(x)     // 10 — doublePtr() changed the original

No pointer arithmetic, and nil pointers

C lets you increment a pointer to walk through memory (ptr++); Go does not allow this at all — it's a compile error. This single restriction eliminates buffer overruns and a large share of classic C memory bugs. A pointer's zero value is nil, meaning it points to nothing; dereferencing a nil pointer (*p when p == nil) causes a runtime panic, so code that receives a pointer typically checks for nil before dereferencing it.

Which is the fix for the Bio() panic at the top of this page — check before the dereference, and return a sensible default instead of crashing:

Gonilptr_fix.go
func (u *User) Bio() string {
    if u.Profile == nil {
        return ""
    }
    return u.Profile.Bio
}

u := &User{}
fmt.Printf("%q\n", u.Bio())   // "" — no panic

Sources

1
The Go Authors. The Go Programming Language Specification, "Pointer types" and "Address operators", go.dev/ref/spec.