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 = a[0:len(a):len(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.