You might hear people call Go a call-by-value language. This means that when you supply a variable for a parameter to a function, Go always makes a copy of the variable’s value. You can run this code on The Go Playground. It is also in the examples/pass_value_type directory.
Let’s take a look. First, define a simple struct:
type person struct {
age int
name string
}
Next, write a function that takes in an int, a string, and a person, and modifies their values:
func modifyFails(i int, s string, p person) {
i = i * 2
s = "Goodbye"
p.name = "Bob"
}
Call this function from main and see whether the modifications stick:
func main() {
p := person{}
i := 2
s := "Hello"
modifyFails(i, s, p)
fmt.Println(i, s, p)
}
As the function name indicates, running this code shows that the function won’t change the values of the parameters passed into it:
2 Hello {0 }
I’ve included the person struct to show that this isn’t true just for primitive types. If you have programming experience in Java, JavaScript, Python, or Ruby, you might find Go’s struct behavior strange. After all, those languages let you modify the fields in an object when you pass an object as a parameter to a function. I’ll cover the reason for this difference when I talk about pointers.
The behavior is a little different for maps and slices. Let’s see what happens when you try to modify them within a function. You can run this code on The Go Playground. It is also in the examples/pass_map_slice directory.
You’re going to write two functions: one to modify a map parameter and one to modify a slice parameter:
func modMap(m map[int]string) {
m[2] = "hello"
m[3] = "goodbye"
delete(m, 1)
}
func modSlice(s []int) {
for k, v := range s {
s[k] = v * 2
}
s = append(s, 10)
}
Call these functions from main:
func main() {
m := map[int]string{
1: "first",
2: "second",
}
modMap(m)
fmt.Println(m)
s := []int{1, 2, 3}
modSlice(s)
fmt.Println(s)
}
When you run this code, you’ll see something interesting:
map[2:hello 3:goodbye]
[2 4 6]
For the map, it’s easy to explain what happens: any changes made to a map parameter are reflected in the variable passed into the function. For a slice, it’s more complicated. You can modify any element in the slice, but you can’t lengthen the slice. This is true for maps and slices passed directly into functions, as well as for map and slice fields in structs.
This program leads to the question: Why do maps and slices behave differently than other types? It’s because maps and slices are both implemented with pointers, discussed in this topic.
Tip
Every type in Go is a value type. It’s just that sometimes the value is a pointer.
Call by value is one reason that Go’s limited support for constants is only a minor handicap. Since variables are passed by value, you can be sure that calling a function won’t modify the variable whose value was passed in (unless the variable is a slice or map). In general, this is a good thing. It helps you understand the flow of data through your program when functions don’t modify their input parameters and instead return newly computed values.
While this approach is easy to understand, at times, you’ll need to pass something mutable to a function. What do you do then? That’s when you need a pointer.