Every type in Go is a value type. Each variable holds its own copy of the data, regardless of its type. This might come as counterintuitive since some types behave like reference types. In Go, there are types that behave similarly to reference types in other languages, such as slices, maps, channels, functions, and pointers. These types hold a reference to the underlying data, so when you assign a variable of these types to another, they both refer to the same underlying data. However, it’s important to note that these are not true reference types. They are still value types, but the value they hold is a reference to the underlying data.

When using * operator to create a new variable, a copy of the original variable is created. The original variable behind the pointer is unaffected.

t.Run("indirection is a copy", func(t *testing.T) {
    type Foo struct {
        x int
    }

    pointer := &Foo{x: 1}
    samePointer := pointer
    samePointer.x = 2

    assert.Equal(t, pointer, samePointer, "both point to the same memory location")
    assert.Equal(t, 2, samePointer.x)
    assert.Equal(t, 2, pointer.x)
    if &pointer == &samePointer {
        t.Fatalf("pointers themselves are stored in different memory locations")
    }

    indirection := *pointer
    indirection.x = 3

    assert.Equal(t, 3, indirection.x, "indirection operator * creates a copy")
    assert.Equal(t, 2, pointer.x, "copy behind the old pointer is unaffected")
    assert.Equal(t, 2, samePointer.x, "copy behind the new pointer is unaffected")
})

When passing a pointer to a function, a copy of that pointer is created. They both point to the same address, but changing what copiedPointer points to will not have any affect on the original pointer. To update the original value, pointer must be dereferenced.

t.Run("passing pointers", func(t *testing.T) {
    x := 10
    pointer := &x
    failedUpdate := func(copiedPointer *int) {
        if &pointer == &copiedPointer {
            t.Fatalf("pointers themselves are stored in different memory locations")
        }
        y := 11
        assert.Equal(t, 10, *copiedPointer, "original data is there")
        copiedPointer = &y
        assert.Equal(t, 11, *copiedPointer, "points to the new data")
    }

    failedUpdate(pointer)
    assert.Equal(t, x, 10, "original variable is uneffected")

    goodUpdate := func(copiedPointer *int) {
        *copiedPointer = 12
    }
    goodUpdate(pointer)
    assert.Equal(t, x, 12)
})

passing pointer to a function

Even though Go’s pointers are simpler than C’s, they can still surprise developers that are new to them.