01. Introduction
The usual slice syntax in Golang is a[low:high]
, which
you are probably familiar with. There is also another slice syntax in the
form of a[low:high:max]
, which takes three indexes instead
of two. What does the 3rd index max
do? Hint: It is not the
step
index in the Python slice syntax
a[low:high:step]
.
Answer: The 3rd index is for setting the capacity of the slice! It is called a “full slice expression” in the Golang specification.
02. Understanding Golang Slices
To understand why this was added to Golang and how it is useful, let’s start with arrays and pointers.
Out-of-bounds errors are common in C programs, and Golang mitigates this problem by having built-in runtime bounds checkers. Bounds checking for arrays is simple because Golang arrays are of fixed length, however, bounds checking for pointers is not so simple, because the bounds of pointers are not explicitly defined. Slices in Golang are just one solution to bounds checking for pointers.
Instead of using plain pointers to access array elements, Golang augments pointers with a length field; the result (pointer-with-length) is then called a “slice”, or “fat pointer” elsewhere. With the length field, runtime bounds checking is easy.
Golang slices are not just pointer-with-length, they also have a
“capacity” field, because growing a dynamically allocated array is such
a common task. And the capacity of a slice also acts as a bounds checker
for the slice expression a[low:high]
— the end of a slice
cannot exceed its capacity.
03. Understanding
a[low:high:max]
The slice index expressions are bounds-checked by the length field, and the length field can be reduced by slicing to provide the desired bounds-checking.
Likewise, one might wonder if it is possible to reduce the capacity
of a slice to tighten the bounds check for the slice expression
a[low:high]
. For example, the following expression reduces
the capacity of a slice to its length:
= a[0:len(a):len(a)] a
After this, the slice a
is restricted to itself, the
elements past the end of the slice cannot be accessed or modified, even
if you accidentally re-slice or append
to it.
This trick is useful for returning a slice from an immutable array;
if you accidentally append
to the supposedly immutable
slice, a copy is forced and no data is overwritten because there is no
more capacity left.
This form of slice expression is called a “full slice expression” in the Golang specification.