Passing by pointer
Go passes function arguments by value. When you call a function with an int, the function receives a copy of the int. Anything it writes to that parameter touches only the copy; the caller's variable is untouched. That rule is easy to see and impossible to work around with plain values.
The problem: writes inside a function do not leak out
Try to predict what this prints before you read the answer:
func setTo100(n int) {
n = 100
}
func main() {
x := 5
setTo100(x)
fmt.Println(x)
}
Output:
5
setTo100 received its own local n, overwrote that n with 100, and returned. The caller's x was never involved in any of it. If you expected setTo100 to change x in place, you have met the call-by-value rule.
The fix: pass a pointer
Pass the address of x and have the function write through the pointer. The function still receives a copy (of the pointer), but this time the copy and the original aim at the same storage:
func setTo100(n *int) {
*n = 100
}
func main() {
x := 5
setTo100(&x)
fmt.Println(x) // 100
}
Three small changes from the broken version:
- The parameter type is
*intinstead ofint: the function accepts a pointer to an int. - The body uses
*n = 100(dereference-and-assign) instead ofn = 100. - The call site passes
&xinstead ofx.
That three-line pattern is how you write any function that needs to modify a caller's variable. Every receiver-method in the next chapter that mutates its struct uses the same shape.
When to reach for a pointer parameter
- You need to modify the caller's value. The
doubleexample in the Try it below is the minimal shape of this. - The value is large and copying it on every call would be wasteful. A struct with many fields (coming next chapter) copied on every call costs something; a pointer is always one machine word regardless of what it points at.
If neither applies, take the value directly. A function that receives a pointer just to read from it is harder to reason about (the pointer might be nil, the data might change behind your back), so make callers pay the pointer cost only when it genuinely helps.
Slices and maps are already reference-like: passing a slice to a function lets the function modify the underlying array without any pointer involved. You almost never need a pointer to a slice or a map. (Channels, covered in the Concurrency chapter, behave the same way.) Pointers in Go are for the cases those built-in types do not cover: individual values, and structs.
The starter declares x := 7 and calls this broken double:
func double(n int) {
n = n * 2
}
It compiles, but x never changes. Fix double so that double(&x) updates x in place, using the three-change pattern from this lesson (parameter type, body, call site). After a correct fix, the printed value of x should be 14.
14