Go Slice Gotcha
Go is, mostly, a straightforward and unsurprising language.
One thing that caught me recently was the behavior of slices.
The setup
nums := []int{3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}
chunk := nums[3:6]
fmt.Println("chunk:", chunk) // chunk: [6 7 8]
A slice and a subslice; no surprises yet.
chunk = append(chunk, 999)
What are the consequences of this append
? Spoilers ahead!
Oh no…
fmt.Println("nums: ", nums) // nums: [3 4 5 6 7 8 999 10 11 12 13 14]
fmt.Println("chunk:", chunk) // chunk: [6 7 8 999]
chunk
contains what you would expect.
What about that 999
after 8 in the original slice?!
Slice Capacity
The answer is not the len
, check the cap
len(nums[3:6]) // 3
cap(nums[3:6]) // 9
Why 9? Because the extra capacity of the original slice was carried over when slicing. There were 9 elements from index 3 to the end of the slice:
fmt.Println(nums[3:]) // [6 7 8 9 10 11 12 13 14]
len(nums[3:]) // 9
The Solution
Go has a syntax to limit the cap
when subslicing:
len(nums[3:6:6]) // 3
cap(nums[3:6:6]) // 3
It’s worth reading Slice expressions; especially the “Full slice expressions” subsection.
a[low : high : max]
I found that Go in Action (section 4.2.3) had a great explanation on this:
The purpose [of the third index] is not to increase capacity, but to restrict the capacity.
You’re still sharing the underlying array, within the valid indices. If you try to append, it’s a copy on write.
Conclusion
I’m just surprised this syntax doesn’t work the other way around:
- nums[3:6] // gets only what you asked for?
- nums[3:6:cap(nums) - 3] // gets you that extra capacity?
Ugly? Yes. But I like syntactic vinegar when doing things that aren’t straightforward.