A pointer parameter
In the previous lesson a pointer was a slip of paper with a house's address. Now let's hand that slip to a function—this way the function can change your variable rather than its own copy.
First, recall how a function receives its arguments. It gets a copy of each one—like a photocopy of a document. Whatever the function writes on the photocopy, your original stays the same:
package main
import "fmt"
func inc(n int) {
n++
}
func main() {
x := 1
inc(x)
fmt.Println(x) // 1
}Let's go step by step:
inc(x)passes a copy of the value ofxto the function.n++increments that copyn.fmt.Println(x)prints1:xinmaindid not change.
For a function to change the caller's variable, it needs not a copy of the value but the variable's address.
Two kinds of parameters
A function parameter can hold the value itself or the address of a variable. That decides whether the caller sees the changes.
A value parameter
The parameter n int gets a copy of the number, as in the first example. The call inc(x) never changes x: the function works on its own photocopy.
You choose such a parameter when the function only computes a new value. Then returning the result with return is simpler.
A pointer parameter
If the parameter has the type *int, the function receives a pointer and writes through it. At the call, & goes before the variable—that is how you hand the function the slip with the address:
func inc(n *int) {
*n++
}
func main() {
x := 1
inc(&x)
inc(&x)
fmt.Println(x) // 3
}Step by step:
inc(&x)passes the address ofx.- Inside the function
*nisxitself, and*n++adds one to it. - The second call
inc(&x)adds one more, andfmt.Println(x)prints3.
The function can read through the same pointer too: fmt.Println(*n) inside inc would print the current value of x.
The ampersand at the call also tells the reader "this function may change the variable". The call inc(&x) might change it. So you choose a pointer parameter when the function must change the caller's variable.
Let's put both kinds of parameters into one program. Press "Next step" and watch x in the main frame: it changes only after the call incPtr(&x).
1–4package main · import "fmt"5func incValue(n int) {6 n++7}8 9func incPtr(n *int) {10 *n++11}12 13func main() {14 x := 115 incValue(x)16 fmt.Println(x)17 incPtr(&x)18 fmt.Println(x)19}
main creates the variable x with the value 1.
By value or by reference
A pointer parameter does not break the copying rule: the function still gets a copy—of the slip, not of the house. The copy holds the same address, so it leads to the same x.
Interviewers like to ask this: "Does Go pass arguments by value or by reference?" The answer:
- Go always passes arguments by value;
- a plain parameter copies the number;
- a pointer parameter copies the address.
With pass by reference the function would get the caller's variable itself rather than a copy; Go has no such passing.
Slices and map
Slices are a little trickier. A slice is a small note with three fields:
- a pointer to an array;
- a length;
- a capacity.
The function gets a copy of the note, but the pointer in the copy leads to the same array. So writing to an element inside the function is visible outside. A map works the same way: the copy refers to the same table of keys and values.
package main
import "fmt"
func setFirst(s []int, m map[string]int) {
s[0] = 9
m["a"] = 1
}
func main() {
s := []int{0, 0}
m := map[string]int{}
setFirst(s, m)
fmt.Println(s, m) // [9 0] map[a:1]
}Let's go through the example step by step:
setFirst(s, m)gets copies of the slice's note and of themap.s[0] = 9writes into the shared array, andm["a"] = 1into the shared table.fmt.Println(s, m)inmainprints[9 0] map[a:1]: both writes are visible outside.
You don't need a pointer to a slice or to a map for this. But a slice's length lives in the note itself, and the note is a copy. If the function calls append, the caller does not see the new length. That is why a function that adds elements returns the new slice, the way append does.
Common mistake
A function with a *int parameter was given the variable instead of its address:
x := 1
inc(x) // ✗
fmt.Println(x)cannot use x (variable of type int) as *int value in argument to incThe program does not compile: the function expects a slip with an address and gets a number. The fix is inc(&x). The reverse mistake does not compile either: a parameter of type int cannot take &x, because &x has the type *int, not int.
Key points
- Go always passes arguments by value: the function gets a copy.
- To let a function change your variable, pass its address: a
*intparameter, the callinc(&x). - A slice or
mapargument shares its data with the original, but the caller does not see a new length afterappend.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.