In the previous chapter, we discuss how to use append function in Golang. append
is a built-in function which appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself.
The signature of append:
func append(slice []T, elements ...T) []T
where T is a placeholder for any given type. You can't actually write a function in Go where the type T
is determined by the caller. That's why append
is built in: it needs support from the compiler.
Example 1: Simple usage of append function
Here is an example of using append 1 or multiple elements to a slice:
package main
import "fmt"
func main() {
slice0 := []int{2, 5,13,15}
fmt.Println("slice 0:", slice0)
// append 1 element
fmt.Println("after the first append:", append(slice0, 4))
// append multiple elements
fmt.Println("after the second append:", append(slice0, 3, 5, 7))
}
Output
slice 0: [2 5 13 15]
after the first append: [2 5 13 15 4]
after the second append: [2 5 13 15 3 5 7]
What append
does is append the elements to the end of the slice and return the result. The result needs to be returned because, as with our hand-written Append
, the underlying array may change. You can see that the original slice is not affected after the first append because we do not assign return append value to the original slice.
Example 2: Update the slice itself after calling append function
In the example below, we will assign the return append value to the original slice:
package main
import "fmt"
func main() {
slice0 := []int{2, 5,13,15}
fmt.Println("slice 0:", slice0)
// append 1 element
slice0 = append(slice0, 4)
fmt.Println("after the first append:", slice0)
// append multiple elements
slice0 = append(slice0, 3, 5, 7)
fmt.Println("after the second append:", slice0)
}
Output:
slice 0: [2 5 13 15]
after the first append: [2 5 13 15 4]
after the second append: [2 5 13 15 4 3 5 7]
Example 3: Unintended side effects when using append function
In this example, we will first create an int slice:
firstSlice := []int{12, 6, 19, 98}
Do the following steps:
- Initialize a new slice (secondSlice) which contains the first three elements of the firstSlice (firstSlice[:3]) and appending 1 element to it.
- Initialize a new slice (thirdSlice) by appending 1 element to the firstSlice
secondSlice := append(firstSlice[:3], 59)
thirdSlice := append(firstSlice, 820)
Now we will print out these 3 slices:
package main
import "fmt"
func main() {
firstSlice := []int{12, 6, 19, 98}
secondSlice := append(firstSlice[:3], 59)
thirdSlice := append(firstSlice, 820)
fmt.Println("the first slice:", firstSlice)
fmt.Println("the second slice:", secondSlice)
fmt.Println("the third slice", thirdSlice)
}
Output:
the first slice: [12 6 19 59]
the second slice: [12 6 19 59]
the third slice [12 6 19 59 820]
The output of the secondSlice is easy to understand. But how about the firstSlice and thirdSlice? You may think that the firstSlice is not affected by the append function call. But why both firstSlice and thirdSlice are changed?
You must first understand the proper definition of a slice in order to comprehend this behavior. Slices hold references to an underlying array, and if you assign one slice to another, both refer to the same array. The length of a slice may be changed as long as it still fits within the limits of the underlying array; just assign it to a slice of itself. The capacity of a slice, accessible by the built-in function cap
, reports the maximum length the slice may assume.
In other words, the underlying array will not be assigned to new memory address if it can fit the additional element(s). In the case of there is insufficient capacity, a new array will be made. Now you can understand why the firstSlice is affected by the append function (because both firstSlice and secondSlice refer to 1 underlying array).
Summary
The idea of appending to a slice is so useful it's captured by the append
built-in function. We must return the slice afterwards because, although Append
can modify the elements of slice
, the slice itself (the run-time data structure holding the pointer, length, and capacity) is passed by value.
References
https://pkg.go.dev/builtin#append
https://go.dev/doc/effective_go#append