Go Slices

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

External

Internal

Overview

A slice is a descriptor, or a header that defines a contiguous segment of an underlying array, stored separately from the slice variable. A slice is not an array, a slice describes a piece of an array, and it is the idiomatic Go way of manipulating sequential, indexed data. Given that declaring a slice will create the backing array if no array is explicitly provided, you can almost always use a slice instead of an array. The slice provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type.

A slice contains a pointer to the underlying array, a length and a capacity. More details are available in the Structure section. A slice, once initialized, is always associated with the underlying array that holds its elements. The slice shares storage with its array, and other slices of the same array. A distinct slice that shares the underlying array with an original slice can be created by slicing the original slice with a slice expression.

Once the slice is initialized and the association with the underlying array is established, the association never changes, and the underlying array never changes, it cannot be grown or shrunk. However, a slice can be grown beyond the limits imposed by the original array, by allocating a new array under the covers, creating a new slice, copying elements across and using the second slice as the first slice. For more details, see append() below.

Slices are passed by value, like an other Go variable, but when that happens, the slice argument of a function and the internal variable copy of the slice still share the underlying array.

The Go documentation used to refer to slices as reference types, but not anymore. The "reference type" terminology was removed from Go documentation. The slices are sometimes referred to as dynamic arrays.

Structure

Under the covers, a slice is a data structure that contains a pointer, the length of the slice and the capacity of the slice. These elements are referred to as contents, fields or components of the slice. You can think of it as being built like this:

type sliceHeader struct {
    fistElement *T // pointer to the underlying array element that is the first element of the slice
    length      int
    capacity    int
}

Pointer

The pointer field contains a pointer to the underlying array element that represents the first element of the slice. The slice indexing is zero-based, so technically this would be slice element with the index 0.

Length

A slice's length represents the number of elements in the slice. "Length" and "size" can be used interchangeably. Unlike for arrays, the slice length can change. Describe how the length of a slice can change. Is there any other way than append()?

If an element beyond the length of the slice is accessed for read or write with the index operator, the Go runtime panics, even if the slice has sufficient capacity:

panic: runtime error: index out of range [4] with length 3

The same is true if an element is accessed with a negative array.

The length of a slice is reported by the len() function.

Capacity

Slices and Pass-by-Value

Go uses pass-by-value, so when a slice argument is passed to a function, the internal fields are copied across on the function stack, including the pointer to the underlying array. Therefore, the underlying array data structure is intrinsically shared, even in case of pass-by-value. Both the original slice header and the copy of the header passed to the function describe the same array. If the function changes the underlying array via its copy of the slice, the modified elements can be seen outside the function through the original slice variable.

However, if the function modifies the elements of the slice, like the length, or the pointer to the array element, those changes naturally do not propagate outside of the function. Only the changes to the underlying array do.

The slice variables are small, so their content can be copied fast. The fact that they include a pointer to the underlying array prevents copying the array, which has important performance implications, especially if the underlying array is large.

Note that slices are not reference variables, as Go does not support reference variables, so no two different slice variables may point to the same slice instance.

Pointers to Slices

It is idiomatic to use a pointer receiver for a method that modifies a slice.

nil and Empty Slice

nil Slice

A slice can be tested that it is nil with:

s == nil

which will return true.

Declaration and Initialization

Initialization with make()

Initialization with a Composite Literal

Initialization by Slicing the Underlying Array

Given an array variable, a slice of it can be declared by using the slice expression.

var aa [100]int // zero-value array declaration
var ss = aa[10:20] // slice declaration with slice expression

Operators

Indexing Operator []

Negative indexes are not supported, attempting to access a slice with a negative index generates a runtime panic.

Slice Expressions

Slice Expressions

Slice Functions

len()

Reports the slice length.

cap()

copy()

append()

append() changes the length of the slice.

make()

See Initialization with make() above.

new()

The slices Package

The slices Package

Iterating over a Slice

Iterating over a nil Slice

range applied to a nil slice does not panic and iterates zero times.

TODEPLETE

Go Slices TODPLETE

Deplete https://go.dev/wiki/SliceTricks into this page.