Today, we are going to talk about slices. They are pretty similar to arrays, but we'll use slices more often than arrays because of their flexibility.
Slice declaration
A slice declaration looks like an array declaration but without specifying the size:
var s []int
Since the size is not a part of the type, you can assign slices of different lengths to this s slice variable:
s = []int{0, 1, 3}
s = []int{120, 56}
s = []int{43, 42, 12, 12}
And just like with the arrays, you can retrieve items using [].
s[0] = 20
fmt.Println(s[0]) // Will print 20
The core difference between an array and a slice is that an array holds the data, whereas a slice holds a pointer to the array that holds the data. Just like with a pointer, the default value for a slice is nil. Slices also exhibit pointer-like behavior when they are uninitialized.
var s []int // Declared, but not initialized slice of integers;
// s is equal to nil
s[0] = 10 // Panic: index out of range [0] with length 0
Like in the second part of the example above, you can manually assign a predefined slice to a slice variable. But what can you do if you need a slice of a thousand elements?
Initialization
The make function is here to save the day. Similar to the new function, it can allocate and initialize a new slice variable. While the new function only takes the object type as its argument, the make function takes a length expression as the second argument and a capacity variable as the third argument (both must be of the integer type). The make function returns a slice, not a pointer to a slice, since a slice is already a reference to the underlying array.
var s []int
s = make([]int, 6) // You can drop the capacity argument if you have no need for it;
// In this case, cap will be equal to len
The length variable of the make function represents how many elements with the default value will be created in the slice. It also specifies its size. The capacity variable, in turn, outlines how far we can extend our slice without reallocating the underlying array.
You can do slice extension via re-slicing — we will discuss it in an upcoming topic. Speaking of the length and the capacity of an array or a slice, you can get the first one with the help of the len function, and the second one by using the cap function:
var length = len(s)
var capacity = cap(s)
As you can probably see, an array and a slice are somewhat like a variable and a pointer, correspondingly:
-
Both a slice and a pointer require initialization through a special built-in function;
-
Both have
nilas the default value; -
Both contain not the data itself but a reference to it.
Making a multi-dimensional slice
The make function initializes only the root type. If you have a slice of slices, you need to initialize all the slice elements manually. In the example below, we initialize a 2D slice of integers with square sides of the length 10. No need to worry if you are not yet familiar with the for and range keywords. Here, we iterate over the slice and initialize a sub-slice of the length 10 at each step of the iteration.
var mds [][]int // Declaring a 2D slice of integers
mds = make([][]int, 10) // Initializing the root slice
fmt.Println("mds =", mds) // mds = [[] [] [] [] [] [] [] [] [] []]
for i := range mds { // Looping over the root slice
mds[i] = make([]int, 10) // and initializing all its slice elements
}
If we had a 3D slice, we would need a second loop inside the first one. The first loop would initialize the [][]int slice elements of the root [][][]int slice, whereas the second one would initialize the []int slice elements of the [][]int slices. Don't worry, we'll not ask you to do such initializations in the practice section. Just keep them in mind, as they might come in handy later.
Slice assignment
Earlier, we mentioned similarities between a slice and a pointer. Let's look at another thing that they have in common. Imagine we have a slice. We naively create a new slice from the old one, and then we change several elements of our new slice:
var s = []int{12, 23, 34}
var sn = s
sn[0] = 0
sn[1] = 11
The thing is, the sn slice here is not a proper copy of the s slice. When you print both of them after these assignments, you'll see that they store identical elements:
fmt.Println(s) // [0 11 34]
fmt.Println(sn) // [0 11 34]
As you may recall, a slice is a reference to an underlying array. When you declare and assign a new slice with the var sn = s statement, you copy only the pointer, not the actual data. Thus, s[i] and sn[i] point to the same element within the same underlying array.
On the other hand, if s was an array, the var sn = s statement would result in copying the data from s to sn, as we expected.
var s = [3]int{12, 23, 34} // Here we simply specify the size
// to convert the slice into an array
var sn = s
sn[0] = 0
sn[1] = 11
fmt.Println(s) // [12 23 34]
fmt.Println(sn) // [ 0 11 34]Conclusion
Today we've picked up basic knowledge about slices. In particular, we've covered the following:
-
How to declare and initialize a slice;
-
How to work with its elements and with the slice itself.