What do “value semantics’” and “pointer semantics” mean in Go?

When you call a function or a method and you pass parameters to it, a copy is made from the values, and the function can only access these copies.

This means if the function attempts to modify / change the copies, it will not change the original value.

For example:

func main() {
    i := 1
    fmt.Println("double:", double(i))
    fmt.Println("original i:", i)
}

func double(i int) int {
    i *= 2
    return i
}

This outputs (try it on the Go Playground):

double: 2
original i: 1

Even though double() modifies its i parameter, the variable (whose value was passed) at the caller did not change.

For it to change, we would need to change the signature to expect a pointer, pass a pointer, and modify the pointed value:

func main() {
    i := 1
    fmt.Println("double:", doublep(&i))
    fmt.Println("original i:", i)
}

func doublep(i *int) int {
    *i *= 2
    return *i
}

This outputs (try it on the Go Playground):

double: 2
original i: 2

So if we pass something, we’re expecting the original value not to change if the passed value is modified, unless we pass a pointer to it.

Pointer semantics means that even though we pass something “by value”, the callee can still modify the “original” value, just as if we would have passed a pointer to it.

For example:

func main() {
    is := []int{1, 2}
    fmt.Println("double:", doubles(is))
    fmt.Println("original is:", is)
}

func doubles(is []int) []int {
    for i := range is {
        is[i] *= 2
    }
    return is
}

This outputs (try it on the Go Playground):

double: [2 4]
original is: [2 4]

Even though we did not pass a pointer (is is not a pointer), the calle modified its elements, and the value of the original slice also changed.

We say that even though in Go everything is passed by value, passing slices have pointer semantics, because if the callee modifies the elements, it will be reflected in the original.

Reasoning

Everything in Go is passed by value, slices too. But slices are–under the hood–struct-like data structures that hold a pointer to an underlying array that holds the actual elements. And when you pass a slice, a copy is made, but only this slice header will be copied (this is the slice value). The copy will hold the same pointer, pointing to the same backing array. The backing array is not copied. So when the callee modifies the elements of the slice, the elements of the backing array are modified, which is the same as the backing array of the original slice.

Read more about it here: Are golang slices pass by value?

There are many types that have pass by pointer semantics, such as slices, maps, channels.

It is worth noting that–unlike slices–arrays are not in the line, an array value means all its values, and passing an array makes a copy of all its elements.

Leave a Comment