From charlesreid1

Basics of Go Slices

Slices vs Arrays

As mentioned on the Go/Arrays page, there is a subtle but important difference between arrays and slices.

Typically, arrays are not used directly, since the exact size of the array must be known. (Similar to Java.)

Rather, we use array slices, which are small data structures wrapping a reference to an array. These allow the size to change.

To create a range type variable:

var myslice []int

Note that this does not initialize the slice, it just allocates the variable as that type.

To actually initialize the slice, we have several options:

Initialize using the builtin make() function:

// Create a slice with the specified capacity using make()
myslice := make([]int,3)

Create empty slice:

// Create an empty slice
emptyslice := []int{}

Use builtin append() function:

// Create an empty slice and populate it
var keys []int
for k:=0; k<10; k++ {
    keys = append(keys,k)
}

Analogies with Java

While slices/arrays are more convoluted than Java's array and ArrayList data structures, there are some analogies.

  • arrays in both languages must have size specified at creation time, which is often inconvenient
  • the ArrayList class wraps the array and handles expanding capacity as needed, provides more convenient high level append/add methods
  • the slice data structure also wraps an array. does not handle expanding capacity, but does provide convenient high level append/add methods

Note that we can also do things like:

  • Allocate a slice with a given capacity, which will also create an underlying array with the specified capacity
  • Copy from one slice/array into another slice/array (just slices? arrays-to-slices? slices-to-arrays?)

I think the confusing thing is, we need to keep track of what underlying array data the slice refers to, and that can (I think?) change during the slice's lifetime, or at least, change subtly.

What is a slice

A slice is a "window" view of an array.

The bracket notation with colons is used to refer to a portion of the elements of an array.

Here's an example: we start with an array of bytes, and then take a slice of it:

var buffer [256]byte

...

var slice []byte = buffer[100:150]

//alternatively,
slice := buffer[100:150]

The slice is not simply a reference to the underlying data. The slice should be thought of as a simple data structure with three underlying pieces of data:

  • a pointer to an element of the original array, wherever it lives in memory
  • a length
  • a capacity

We can also take a slice of a slice:

slice2 := slice[5:10]

QUESTION:

Here's a shortcut to drop the first and last elements:

slice3 := slice[1:len(slice)-1]

There are many other one-liners that can be used to manipulate slices and arrays. See Slice Tricks on the Go wiki: https://github.com/golang/go/wiki/SliceTricks


References vs Copies

It is important to distinguish between the slice data structure, which is a small bundle that contains a reference to the underlying array data and a length, and the underlying array data itself.

if we use a slice to modify the underlying array data, those changes will be persistent across functions and scopes. But if we modify the slice variable directly, we are modifying a copy of the slice data structure, and we will discard that copy when we leave that scope.

Reference example: modifying underlying data

The following example modifies the underlying array data:

func AddOneToEachElement(slice []byte) {
    for i := range slice {
        slice[i]++
    }
}

func main() {
    slice := buffer[10:20]
    for i := 0; i < len(slice); i++ {
        slice[i] = byte(i)
    }
    fmt.Println("before", slice)
    AddOneToEachElement(slice)
    fmt.Println("after", slice)
}

Again, this requires us to think about the array slice as a data structure, which contains a pointer to an array and a length. The bundle, the data structure, is not a pointer itself!

Even though the slice (also called the "slice header") is passed by value, the header includes a pointer to elements of an array. Thus, both the original and the copy passed to the function refer to the same underlying array (slots in memory).

Copy example: changing size of copy does not change original

The argument to the SubtractOneFromLength function is a slice, which in this case is a copy of the original slice data structure, containing a reference to the same underlying data.

When we modify the slice variable directly, those changes only affect the copy of the data slice, so the original slice is not affected.

func SubtractOneFromLength(slice []byte) []byte {
    slice = slice[0 : len(slice)-1]
    return slice
}

func main() {
    fmt.Println("Before: len(slice) =", len(slice))
    newSlice := SubtractOneFromLength(slice)
    fmt.Println("After:  len(slice) =", len(slice))
    fmt.Println("After:  len(newSlice) =", len(newSlice))
}

Array Slice Capacity

The capacity of an array is fixed; to make a new array with a new capacity, use the make() function.

The make function allocates space in memory for an array slice with a specified capacity.

The make function takes three arguments: the type of the slice, its initial length, and its capacity, which is the length of the array that make allocates to hold the slice data.

// Make an array slice with a length of 100 and room for 5 more
slice := make([]int, 100, 15)

To double the array slice capacity:

    // Original slice:
    slice := make([]int, 10, 15)
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))

    // New slice (double the capacity):
    newSlice := make([]int, len(slice), 2*cap(slice))
    for i := range slice {
        newSlice[i] = slice[i]
    }

    // Replace old with new
    slice = newSlice
    fmt.Printf("len: %d, cap: %d\n", len(slice), cap(slice))

The above code is functional but awkward; Go provides a copy function to copy an old array into a new array, to accomplish the above in fewer lines.

newSlice := make([]int, len(slice), 2*cap(slice))
copy(newSlice, slice)

Note that copy is smart - it will copy as much as it can, but will respect the lengths of both array slices rfg.

Array Slice Insertion

This was an exercise in futility. See Go/Arrays/Array Slice Insertion

Slice Tricks

Communty Go wiki: https://github.com/golang/go/wiki/SliceTricks

Go 101 book: https://github.com/go101/go101/wiki

Flags