Go is call by value

Go is a call by value language. What that means is that when you call a function and pass parameters, those parameters (variables) are copied into new variables. If you work with primitive types in your function, and modify the input parameters, the change will not happen. See this example in the go playground also extracted from Jon's book:

package main

import "fmt"

type person struct {
	age  int
	name string

func modifyFails(i int, s string, p person) {
	i = i * 2
	s = "Goodbye"
	p.name = "Bob"

func main() {
	p := person{}
	i := 2
	s := "Hello"
	modifyFails(i, s, p)
	fmt.Println(i, s, p)

The output is:

2 Hello {0 }

So nothing changes. That is expected. Go is creating new variables when we run the code in the function. Those variables are garbage collected and disappear when the execution flow returns to the main function. But what about composite types like slices and maps?

Here is another playground chunk. And the output is:

map[2:hello 3:goodbye]
[2 4 6]

What is happening here? Parts of the slide mutations work and we are able to modify the map. The explanation is that those types are implemented using pointers. Pointers are nothing more than addresses of memory that point to a variable location.

When you pass a map to a function, you are passing a pointer to that map. Go copies that variable but that variable is just a memory address so you can still reference the original map.

Slices are a bit more complicated. The mutations to the individual elements of the slice work for the same reason. Slices are implemented with pointers. But why the append does not work? It doesn't work because the built-in function append returns a new slice (a new pointer) and we assign it to a variable that only lives within the function. When we return, the variable is garbage collected.