Slices
In the previous lesson the row of lockers was bolted to the floor: an array's length must be known in advance. But the number of items in a cart or lines in a file is known only while the program runs.
That is why arrays are rare in Go code. More often, code uses a slice—a sequence of elements of one type whose length is not part of the type:
package main
import "fmt"
func main() {
s := []int{10, 20, 30}
fmt.Println(s[1], len(s)) // 20 3
s[0] = 15
fmt.Println(s) // [15 20 30]
}A slice is like a window through which you see part of a row of lockers:
- the row itself is an array that Go creates for the literal
[]int{10, 20, 30}without you noticing; - through the window you name locker numbers, take things out and put things in;
- a slice's length is how many lockers are visible in the window.
The diagram shows the slice s from the example. The literal created a row of exactly three lockers, and the window shows all of them.
len is how many lockers are visible in the window, cap is how many there are from the start of the window to the end of the row. A literal creates a row exactly as long as its elements, so both numbers are 3 for s.
Let's walk through the example line by line:
s := []int{10, 20, 30}creates a slice of three elements.s[1]reads the second element,20, andlen(s)returns3, so the line prints20 3.s[0] = 15puts a new value into the first locker.- The second
Printlnprints[15 20 30].
Notation and indexes
A slice type is written without a length in the brackets: []int is a slice of int values. The rest works as it does for an array:
- indexes go from
0tolen(s) - 1; - the
lenfunction returns the slice's current length; - the literal
[]int{}creates a slice with no elements: its length is0, andfmt.Printlnprints it as[].
Slice or array
The slices []int{1, 2} and []int{1, 2, 3} have the same type, []int. So one function with a []int parameter accepts a slice of any length. Arrays can't do that: a function with a [3]int parameter accepts only arrays of three elements.
Interviewers often ask for the main difference between an array and a slice:
- an array's length is part of its type and never changes;
- a slice's length is not part of the type, and the elements themselves live in the array the slice looks at.
Comparing slices
Interviewers often ask this one too: can you compare two slices with ==? Arrays of the same type can be compared: == checks their elements in order.
a := [3]int{1, 2, 3}
b := [3]int{1, 2, 3}
fmt.Println(a == b) // trueFor two slices, the same s == t does not compile:
invalid operation: s == t (slice can only be compared to nil)Remember that a slice is a window onto a row of lockers. Two windows may look at the very same row or at two different rows holding the same things. From s == t alone it is unclear which of these equalities you meant, and Go does not choose for you. The contents of slices are compared in one of two ways:
- go over both slices in a loop and compare the elements one by one;
- call the
slices.Equal(s, t)function from theslicespackage, which does the same.
Going over a slice in a loop
A slice can be gone over with four forms of loop. Let's look at each.
Index and element
The loop for i, v := range s goes over a slice from the first element to the last. On each iteration i is the index and v is a copy of the element at that index:
s := []int{4, 8, 15}
for i, v := range s {
fmt.Print(i, ":", v, " ") // 0:4 1:8 2:15
}The loop runs three iterations:
iis0andvis4.iis1andvis8.iis2andvis15.
All the pairs print on one line: 0:4 1:8 2:15.
This is where the window analogy ends: v is not the locker itself but a copy of what it holds. Writing v = 0 in the body changes only the copy, and the slice stays the same. To change an element in a loop, write s[i] = 0.
Element only
If the index is not needed, _ takes its place: for _, v := range s. With an ordinary name instead of _, the unused variable would stop the build: declared and not used: i.
Index only
If the element is not needed, it is left out entirely: for i := range s. This form is handy when elements are changed through s[i].
A three-part loop
A three-part loop also goes over a slice: for i := 0; i < len(s); i++. The range form is shorter, and its index never goes past the end of the slice. In a three-part loop you write the bound yourself, and people often get it wrong.
Common mistake
A three-part loop runs up to and including len(s):
s := []int{10, 20, 30}
for i := 0; i <= len(s); i++ { // ✗
fmt.Println(s[i])
}panic: runtime error: index out of range [3] with length 3The program prints three numbers and stops on the fourth iteration. The index 3 is outside a slice of length 3, and Go checks this while the program runs. Such an abnormal stop is the panic you have already met: the lines after it do not run. Use < instead of <=, or go over the slice with range.
Key points
- A slice is a sequence of elements of one type; its length is not part of the type.
- A slice's indexes go from
0tolen(s) - 1, and going out of bounds causes a panic. - In
for i, v := range sthe variablevis a copy of the element, and an element is changed throughs[i]. - Two slices cannot be compared with
==: their contents are compared in a loop or withslices.Equal.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.