Pointers in Go: Addresses, Pass by Value and Pointer Receivers
Chapter 04 of 04

Pointers in Go: Addresses, Pass by Value and Pointer Receivers

& and *, why Go always passes by value, what "reference types" really means, nil pointers, and when to use pointers.

Sabbir Ahmmed3 min read0
On this page

A pointer holds the memory address of a value. Go pointers are simple and safe: there is no pointer arithmetic, and the garbage collector manages memory. You need them for two reasons: to let a function modify the caller's data, and to avoid copying large values.

& and *

&x gives you the address of x.
*p gives you the value at the address stored in p (dereferencing).
go
1
2
3
4
5
6
y := 20
p := &y        // p has type *int and stores the address of y
fmt.Println(p)  // 0xc000012345 (some address)
fmt.Println(*p) // 20
*p = 30         // write through the pointer
fmt.Println(y)  // 30

*&y is just y: take the address, then read the value at it.

Go always passes by value

When you call a function, Go copies every argument. Changing the copy does not affect the caller:

go
1
2
3
4
5
func double(n int) { n *= 2 }

n := 5
double(n)
fmt.Println(n) // 5, unchanged

To modify the caller's variable, pass its address and write through the pointer:

go
1
2
3
4
func doublePtr(n *int) { *n *= 2 }

doublePtr(&n)
fmt.Println(n) // 10

Even here Go passes by value: it copies the pointer (the address). Both copies point at the same int.

Pointers to arrays and structs

Passing a large array copies all of it. Passing a pointer copies 8 bytes:

go
1
2
3
4
5
6
func print(nums *[3]int) {
	fmt.Println(nums[0]) // Go automatically dereferences for indexing
}

s := [3]int{1, 2, 3}
print(&s)

Structs work the same way, and Go auto-dereferences field access, so p.Name works whether p is a struct or a pointer to a struct (no -> like C).

go
1
2
3
4
5
type Person struct{ Name string }

func rename(p *Person, name string) {
	p.Name = name // same as (*p).Name = name
}

new(T) allocates a zero T and returns a *T. In practice &Person{Name: "A"} is more common.

Pointer receivers

The same rule applies to methods. A value receiver gets a copy; a pointer receiver can modify the original.

go
1
2
3
4
5
6
7
8
9
10
type Person struct{ firstName string }

func (p Person) renameValue(n string)    { p.firstName = n } // changes a copy
func (p *Person) renamePointer(n string) { p.firstName = n } // changes the original

p := Person{"Jane"}
p.renameValue("A")
fmt.Println(p.firstName) // Jane
p.renamePointer("B")
fmt.Println(p.firstName) // B

Notice we called p.renamePointer on a value, not (&p).renamePointer. Go takes the address automatically when the variable is addressable.

"Reference types": a more precise mental model

You will often read "slices, maps and channels are reference types". Strictly, Go has no reference types: everything is passed by value. But some values contain pointers internally:

TypeWhat gets copiedEffect of the copy
int, float, bool, string, struct, arraythe whole valueindependent copy
pointerthe addressshares the target
sliceheader {ptr, len, cap}shares elements; append may or may not be visible
map, channela pointer to runtime datashares all data
functiona pointer to code (+ captured vars)shares captured state

This explains the surprising slice case:

go
1
2
3
4
5
6
7
8
func appendTo(s []int) {
	s = append(s, 4) // cap was 3: allocates a NEW array, s now points elsewhere
	s[0] = 100       // modifies the new array, not the caller's
}

s := []int{1, 2, 3}
appendTo(s)
fmt.Println(s) // [1 2 3]

Strings contain a pointer too, but they are immutable, so sharing is invisible.

nil pointers

The zero value of a pointer is nil. Dereferencing nil panics:

go
1
2
3
var p *Person
fmt.Println(p == nil) // true
fmt.Println(p.Name)   // panic: invalid memory address or nil pointer dereference

Check for nil at boundaries, and prefer returning (value, error) over returning a nil pointer that callers might forget to check.

When to use pointers

The function or method must modify the value.
The value is large (big structs) and copying would be wasteful.
You need to express "no value" with nil.
The type contains a sync.Mutex or similar that must not be copied.

Do not use pointers just to "be efficient" with small values like int or small structs. Copying them is cheap, and values avoid heap allocations and nil checks.

Key takeaways

& takes an address, * reads or writes through it.
Go always passes by value; a pointer lets the callee change the original.
Slices, maps and channels contain pointers, so their data is shared when copied.
Use pointer receivers when a method mutates its receiver.
Dereferencing nil panics, so guard your boundaries.
#Golang#Pointers#Memory

Last updated on

Discussion0