The Capacity of Slices in Go
Working through the Tour of Go I came across the following code snippet designed to demonstrate how to create slices of a given length and capacity.
Before running any code examples such as this, I attempt to work out what the programme might do. In this case, I expected to see the following
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=5 [0 0 0]
If you run this code you’ll see that this isn’t quite what happens and that the capacity of slice ‘d’ is actually 3 and not 5.
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
I tried a couple of similar examples but couldn’t explain the reduced capacity of slice ‘d’. My understanding of slices is that they are a data structure that describes a subset of a fixed capacity array. Based on this understanding, I interpret this code snippet as follows:
- ‘d’ is a slice of existing slice ‘c’
- ‘c’ is a slice of existing slice ‘b’
- ‘b’ is a slice of an underlying array that has a fixed capacity for 5 integers
As ‘b’, ‘c’, and subsequently ‘d’ all represent subsets of the same underlying fixed capacity array I couldn’t understand two things about the result:
- that the capacity of ‘d’ was 3 and not 5, and
- if we accept (1) as given, that the capacity of ‘c’ was not 2
At this point, I turned to the this post on the Go Blog, Arrays, slices (and strings): The mechanics of ‘append’. It is extremely well written and well worth a read if you really want to understand the mechanics of slices. But it would have been easy to miss the following statement made mid-way through the post.
“The Capacity field is equal to the length of the underlying array, minus the index in the array of the first element of the slice.”
This sounds like it might explain the behaviour I was seeing, but to fully understand it we need to understand a little bit about the structure of a slice, often referred to as the slice header.
// slice b
slice := sliceHeader{
Length: 0,
Capacity: 5,
ZerothElement: *int,
}For the initial slice ‘b’ this should be fairly self explanatory, we are describing a subset of an array of integers that has capacity 5 but length 0. Now, bearing in mind the statement on slice capacity, we can attempt to create similar slice headers for our other slices, ‘c’ and ‘d’.
// slice c := b[:2]
slice := sliceHeader{
Length: 2,
Capacity: 5,
ZerothElement: &b[0], // element [0] of b
}// slice d := c[2:5]
slice := sliceHeader{
Length: 5,
Capacity: 3, // see text for explanation
ZerothElement: &c[2], // element [2] of b
}In the case of ‘c’ the capacity of the underlying array is 5, the index of the first element in the slice is 0 and so the capacity is 5. In the case of ‘d’ the capacity of the underlying array is still 5, but the index of the first element in the slice is 2 and so the slice ‘d’ has capacity 3. And this is exactly what we see in the results of the example.
One last point to note is that in the results of the code snippet, the length of ‘b’ is zero. This refers to the length of the slice ‘b’ and not the length of the underlying array which is 5. It is easy to trip up and assume that the length of ‘b’ is refering to the length of the array, it isn’t.
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
Mystery solved.
The thing I don’t yet understand is why this definition of slice capacity was designed this way, any hints would be most welcome.