Go Slice Copying: What Really Happens Under the Hood?

ยท

4 min read

In Go, variables are passed by value, which means that when we pass variables as arguments to functions, Go makes a copy of those values for the function to use. However, the behaviour of Slice can be a bit confusing at first glance, especially if we don't know how it works under the hood.

Example 1: Modifying a Slice Without Affecting the Original

To understand this better, let's look at the first example. In this example, we have a function called AppendNumber which appends the number 7 to a given slice. However, even though we modify the slice within the function call, we do not affect the original slice because Go creates a copy of the arguments for the function to work with.

func AppendNumber(slice []int) {  
    slice = append(slice, 7)  
}  

func main() {  
    slice := make([]int, 3, 6)  
    for i := 0; i < len(slice); i++ {  
       slice[i] = i  
    }  
    fmt.Println("before", slice) // Print 0 1 2  
    AppendNumber(slice)  
    fmt.Println("after", slice)  // Print 0 1 2  
}

The AppendNumber function only modifies the copy of the slice within its scope, not the original slice, which is very straightforward.

Example 2: Modifying an Element in Slice

Now let's look at the second example. In this example, we have a function called UpdateNumber which updates an element in the slice at index 1 to the value 7. Unlike the previous example, this time we notice that the original slice is modified.

func UpdateNumber(slice []int) {  
    slice[1] = 7  
}  

func main() {  
    slice := make([]int, 3, 6)  
    for i := 0; i < len(slice); i++ {  
       slice[i] = i  
    }  
    fmt.Println("before", slice) // Print 0 1 2  
    UpdateNumber(slice)  
    fmt.Println("after", slice)  // Print 0 7 2  
}

In the main function, we create a slice, assign values to it, and then print the slice before calling the UpdateNumber function. The output shows the original values of the slice, which are 0, 1, and 2. After calling the UpdateNumber function, we print the slice again, and this time the value at index 1 has changed to 7.

Knowing What Values Get Copied in Go

It seems that we still can influence the original Slice given the result of the second example. To think this through, we would need to know exactly what values are copied to the function arguments in Go. When a slice is passed to a function, the slice header (which contains a pointer to the underlying array, its length and capacity) is copied to the function arguments. The underlying array is not copied.

The slice header that is copied to the argument in the examples will look like this:

type sliceHeader struct {
    Data     uintptr // A pointer to the underlying array
    Length   int     // The number of elements it contains
    Capacity int     // The maximum number of elements it can hold
}

In the first example, when we call the AppendNumber function, a copy of the slice header is created so that any changes made to the slice within the function do not affect the original slice.

In the second example, when we call the UpdateNumber function, a copy of the slice header is created. However, since the underlying array is not copied, both the original slice and the copy point to the same array in memory. Therefore any changes made to the slice (changing the value at index 1) will be reflected in both the original slice and the copy.

Conclusion

To conclude, modifying elements of a slice within a function will affect the original slice if the underlying array is shared between the original slice and the function's copy. If a new slice is created within the function (e.g. using append), the original slice is not affected as we have updated the copy of the slice header to the newly created slice.

By understanding what values get copied to arguments in Go, and how changes to the slice header and array elements can affect the original slice, we can write more reliable and maintainable code. If you are interested in more information about Slice in Go, this article is worth a read: https://go.dev/blog/slices

I hope you have found this explanation of slices in Go useful and informative. If you have any further questions or need additional clarification, please feel free to ask.

Thanks for reading!

Did you find this article valuable?

Support Ray Yang by becoming a sponsor. Any amount is appreciated!

ย