Go provides the ability to access individual elements of arrays and slices by using indices. It is possible because these types are considered ordered sequences.
Apart from accessing specific elements of a slice, it is also possible to perform other index-based operations with slices. In this topic, you'll learn how to work with slice expressions to perform such operations.
Simple slice expressions
In Go, we can use slice expressions to perform a series of operations. Let's first take a look at the syntax of a simple slice expression, which is commonly used to construct a subslice:
s[low:high]The range indexes low and high select which elements of the s slice will appear in the result. Now let's go ahead and take a look at the syntax to create the s1 subslice:
s := []int{2, 3, 5, 7, 11} // len: 5 cap: 5
s1 := s[1:3]
fmt.Println(s1) // [3 5]Take notice that even though the slice element s[1] = 3 at range index low is included within the new subslice s1, the element s[3] = 7 at range index high is not included. In simple terms, high serves as a range boundary for slice elements.
The newly created subslice s1 has the length len equal to the indexes range high-low -> 3-1 = 2 and capacity cap equal to cap(s)-low -> 5-1 = 4:
fmt.Println("len:", len(s1), "cap:", cap(s1)) // len: 2 cap: 4For convenience, we can omit any of the range indexes. A missing low index defaults to zero, and a missing high index defaults to the total length of the slice:
fmt.Println(s[2:]) // [5 7 11] — same as s[2:len(s)]
fmt.Println(s[:3]) // [2 3 5] — same as s[0:3]
fmt.Println(s[:]) // [2 3 5 7 11] — same as s[0:len(s)]Since strings are just sequences of characters, we can use the same syntax we utilize to get a subslice to construct a substring:
star := "Polaris"
s2 := star[0:5] + " Bear"
fmt.Println(s2) // Polar Bear
planets := []string{"Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"}
s3 := planets[5][0:5] + "day"
fmt.Println(s3) // SaturdayDetails of simple slice expressions
When writing a simple slice expression to get a subslice from an array or a string, the low and high indexes are valid if they are in range: 0 <= low <= high <= len(s), otherwise, they are out of range, and you'll get a runtime error. For example:
star := "Polaris"
fmt.Println(star[-1]) // invalid argument: index -1
fmt.Println(star[0:8]) // runtime error: slice bounds out of range [:8] with length 7And when getting a subslice from a slice, the resulting subslice capacity cap is equal to the base slice's capacity minus the low index (i.e., cap(s)-low), rather than the base slice's length len:
teams := []string{"Bayern Munich", "Real Madrid", "Manchester City"}
t := teams[1:2]
fmt.Println(t, "| len:", len(t), "cap:", cap(t)) // [Real Madrid] | len: 1 cap: 2
fmt.Println(t[1:3]) // runtime error: slice bounds out of range [:3] with capacity 2Simple slice expressions support arrays, slices, strings, and pointers to array/slice as well:
consts := new([3]float64)
*consts = [3]float64{3.1416, 2.718, 1.618} // pointer to an array
fmt.Println(consts[1:3]) // [2.718 1.618]
animals := new([]string)
*animals = []string{"cat", "dog", "fish"} // pointer to a slice
fmt.Println((*animals)[1:3]) // ["dog" "fish"]When slicing a pointer to an array, we can simply use the regular s[low:high] syntax. However, when slicing a pointer to a slice, we need to use the (*s)[low:high] syntax.
Finally, if we try to get the subslice of a nil slice, the result will also be a nil slice:
var n []int // nil slice
fmt.Println(n[:]) // []Getting the subslice of a subslice
An important detail is that subslices share a common underlying array with their "parent slice". To further explain the common underlying array concept, let's see what happens if we try to get the subslice of a subslice:
var nums [5]int
nums = [5]int{1, 2, 3, 4, 5}
n1 := nums[0:4] // underlying array of n1 is array nums, n1[2] == nums[2]
n2 := n1[0:3] // underlying array of n2 is array n1, n2[2] == n1[2]
n2[2] = 343 // n2[2] == n1[2] == nums[2], refer to the same underlying array elementThe subslice n1 has nums as its underlying array, while the underlying array of the subslice n2 is n1. This means that n2 has a part of nums as its underlying array too, therefore the n2[2] = 343 assignment updates all three of them:
fmt.Println(nums) // [1 2 343 4 5]
fmt.Println(n1) // [1 2 343 4]
fmt.Println(n2) // [1 2 343]Full slice expressions
The key difference between simple slice expressions and full slice expressions is that the latter has one additional range index max:
s[low:high:max]The above syntax constructs a subslice of the same type and with the same length as the simple slice expression s[low:high]. Essentially, the max index allows you to control the maximum capacity of the resulting subslice.
Now let's write the first full slice expression:
s := []int{2, 3, 5, 7, 11}
s4 := s[1:3:4]
fmt.Println(s4, "| len:", len(s4), "cap:", cap(s4)) // [3 5] | len: 2 cap: 3Just like simple slice expressions, the resulting subslice len is equal to high-low, however, the capacity cap is equal to max-low -> 4-1 = 3.
Full slice expressions are restricted to array, slice, or pointer to array types only! If you tried to use a full slice expression on a string type, you would get the following error:
star := "Polaris"
fmt.Println(star[1:3:2]) // invalid operation: 3-index slice of stringDetails of full slice expressions
The index range for full slice expressions is different from simple slice expressions. The indexes are valid if they are in range: 0 <= low <= high <= max <= cap(s), otherwise, they are out of range:
s := []int{2, 3, 5, 7, 11}
fmt.Println(s[1:3:2]) // invalid slice indices: 2 < 3
fmt.Println(s[1:3:6]) // runtime error: slice bounds out of range [::6] with capacity 5In full slice expressions, only the low index is optional; it defaults to zero in case we omit it:
fmt.Println(s[:3:5]) // [2 3 5]In turn, omitting the high or max index isn't allowed:
fmt.Println(s[1::5]) // middle index required in 3-index slice
fmt.Println(s[1:3:]) // final index required in 3-index slice Conclusion
In this topic, you've learned about slice expressions in Go. In particular, you've covered the following theory:
What are simple slice expressions, and what range indexes they use (
lowandhigh);How to get a subslice and a substring with simple slice expressions;
That subslices share a common underlying array with their "parent slice";
What are full slice expressions, and what range indexes they use (
low,high, andmax);That full slice expressions do not support strings; they are restricted to slices, arrays, or pointers to arrays.
Now it's time to test your knowledge of slice expressions by solving some theory and coding tasks. Let's go!