Go normally uses pass-by-value for function calls. When you pass a variable into a function or method, Go will (under the hood) create a new variable, copy the old variable’s value into it, and then pass the new variable (not the original variable) into the function or method.

Non-pointer values

These types behave as described above and are sometimes called non-pointer values:

  • Strings
  • Ints
  • Floats
  • Booleans
  • Arrays
  • Structs

Here’s an example of how these work. Note that the variables myString and si do not point to the same memory address:

package main

import "fmt"

func stringit(si string) {
	fmt.Printf("string it 1: %p\n", &si)
	si = "zzz"
	fmt.Printf("string it 2: %p\n", &si)
}

func main() {
	myString := "foo"
	fmt.Printf("myString: %p\n", &myString)
	stringit(myString)
	fmt.Println(myString)
}
$ go run main.go
myString: 0xc000096210     # address to value of original variable
string it 1: 0xc000096220  # address *not* the same within stringit() func!
string it 2: 0xc000096220
foo                        # value of myString *not* changed by stringit()

Pointer wrappers

There are some data types for which this Go creates a new variable, but it points to the same underlying memory address as the original value. You should be careful with these types because if you don’t understand this behavior, you can end up modifying your orignal data unexpectedly.

These types behave this way and are sometimes called pointer wrappers:

  • Slices
  • Maps
  • Functions

Here’s an example. Note that the memory address is the same, so when we update the map within the function, the original map gets updated.

package main

import "fmt"

func mapit(m map[string]string) {
	fmt.Printf("mapit 1: %p\n", m)
	m["coffee"] = "yes"
	fmt.Printf("mapit 2: %p\n", m)
}
func main() {
	myMap := map[string]string{"pie": "yes"}
	fmt.Printf("before func call: %p\n", myMap)
	mapit(myMap)
	fmt.Println(myMap)
}
$ go run main.go
before func call: 0xc000072180  # address to value of original variable
mapit 1: 0xc000072180           # address *is* the same within mapit() func!
mapit 2: 0xc000072180
map[coffee:yes pie:yes]         # value of myMap *was* updated by mapit()

Note that you need to be slightly careful here, because if you assign an empty map (shown below) to your variable, you’re actually making a new map (not overwriting the original map with an empty map as I had expected).

func mapit(m map[string]string) {
	fmt.Printf("mapit 1: %p\n", m)
	m = map[string]string{"zzz": "yyy"}
	fmt.Printf("mapit 2: %p\n", m)
}
$ go run main.go
before func call: 0xc000098180  # address to value of original variable
mapit 1: 0xc000098180           # address still the same
mapit 2: 0xc0000981b0           # we made a new var with call to map[string]string{}
map[pie:yes]                    # orivinal var's value not modified