Today we continue our discussion about slices. In this topic, we will learn how to copy slices, how to append elements to a slice, and how we can use the range keyword to iterate over a slice or an array.
For range loop with an array and a slice
To begin with, consider we have a slice.
var s = []int{0, 10, -3, 5, 99}To iterate over a slice, we can write this:
for index := 0; index < len(s); index++ {
// processing slice elements
}Now let's introduce the for range loop:
for index := range s {
// processing slice elements
}These two examples above are logically equal. But as you can see, with the help of the range keyword, we end up writing less code, and it becomes clearer. Furthermore, when using this keyword, you can obtain a copy of a slice element alongside its slice index:
for index, element := range s {
// processing slice elements
}When you need only the copy of a slice element, use _ to indicate that you don't need the index. Otherwise, the Go compiler will throw you the declared but not used error because of the unused index variable.
for _, element := range s {
// processing slice elements
}It is important to understand that the assignment of a new value to a copied element will have no effect on the original one. Thus, the element you receive through the range keyword is for read-only purposes:
for _, element := range s {
element = newValue // this assignment will have no effect
} // on the original slice elementIf you want to change a slice element inside a loop, use its index:
for index := range s {
s[index] = newValue
}The copy function
In Go, we have a special built-in function used to copy slices – the copy function. Its first argument is the destination, and the second one is the source. Excluding one special case, this function works only with slices.
The special case we've mentioned is a string, and we'll discuss it in an upcoming topic.
In addition, the copy function returns the number of copied elements. It will be the minimum of len(source) and len(destination). Let's have a look at the following example:
var s = []int{12, 23, 34}
var sn = make([]int, len(s))
var n = copy(sn, s) // var n is the number of copied elements
sn[0] = 0 // Here, we assign a new value to the slice elements in order
sn[1] = 11 // to see whether it is a proper copy or the same slice
fmt.Println(n) // 3
fmt.Println(s) // [12 23 34] - the initial slice with no changes
fmt.Println(sn) // [ 0 11 34] - the copied slice with modified elementsThe copy function was built in a way that it copies only available elements to available places. It means that if you copy a slice of length 3 to a slice of length 0, the function won't copy anything:
var s = []int{12, 23, 34}
var sn []int
var n = copy(sn, s)
fmt.Println(n) // 0
fmt.Println(sn) // []If the number of copied elements is irrelevant, you can simply write:
copy(sn, s)The append function
The key feature of a slice is its variable size. In Go, we use a built-in function append to extend slices by adding new elements to the end of one. The function's argument is a slice to which we want to add elements, followed by the elements themselves. The append function returns a new extended slice that we should save. Here is an illustration of appending elements to a slice:
var s = []int{12, 23, 34}
s = append(s, 45)
fmt.Println(s) // [12 23 34 45]
s = append(s, 56, 67)
fmt.Println(s) // [12 23 34 45 56 67]After its first argument, the append function can take any number of elements. We have a special syntax for breaking a slice into a series of function arguments:
var s1 = []int{12, 23, 34}
var s2 = []int{45, 56, 67}
var s = append(s1, s2...)
// Like that, we can append one slice to anotherThese three dots after the s2 slice tell Go to break the s2 slice down to a series of arguments for the append function. It is somewhat like doing the following:
var s = append(s1, s2[0], s2[1], s2[2])However, since the size of a slice can change after compilation, the three-dot notation is indispensable for appending one slice to another. What's more, the append function can add elements even to a nil slice, and initialize it:
var s []int // s is []
s = append(s, 10) // s is [10]Slice allocation and reallocation
In the example above, we first have a nil slice with len and cap equal to 0. Clearly, we have no room for any new elements. In this case, the append function will initialize a new backing array with a greater capacity and will copy the data from the old backing array to the new one.
It is good practice to manually initialize a slice of such a capacity that would minimize the need for reallocation.
We can establish the capacity we need from the logic of a task:
var a = []int{1, 2, 4, 3, 6}
var b = []int{-1, 9, -90}
// We want to join two slices, which means we already know
// the capacity of the new slice: len(a)+len(b)
var s = make([]int, 0, len(a)+len(b))
s = append(s, a...)
s = append(s, b...)We can also assume how many elements we'll need. For instance, if we are working on some historical analysis of the world's countries, we can assume how many countries there were in the past and how many there are now and create a slice of countries with an appropriate capacity:
var countries = make([]string, 0, 2000) // len: 0 | cap: 2000It is essential to understand that if we only pass the length without the capacity to the make function, it will return a slice with a capacity equal to its length. In such case, on calling the append function, and appending a new element, Go will reallocate the slice to increase its capacity:
var countries = make([]string, 2000) // len: 2000 | cap: 2000
countries = append(countries, "Indonesia")
fmt.Println("cap:", cap(countries)) // cap: 3072On the other hand, if we assign values of the elements manually, using indexes within the slice range, no reallocation occurs:
var countries = make([]string, 2000) // len: 2000 | cap: 2000
for i := range countries {
countries[i] = "Indonesia" // Here, we assign one and the same value to all
// elements just to show you the slice behavior
}
fmt.Println("cap:", cap(countries)) // cap: 2000Conclusion
Today we have covered two important slice functions – copy and append, and have learned how to perform iterations over a slice or an array by using the for range keywords. Now let's move on to the exercises to ensure all this information stays with us!