Go's Approach To Arrays And Slices

Author: Joe Hutcheson

The concept of arrays in GO might be best approached with a bit of caution and a shift in mindset, especially if you have been programming in other langauges. Conceptually, an array in GO looks a lot like an array in JavaScript or Ruby, or a Pythonic list.

[1, 2, 3, 4] // array JavaScript/Ruby
[1, 2, 3, 4] # list in Python
[1 2 3 4] // array in GO

Aside from a noticeable lack of commas in the GO array, the anatomy remains similar–an ordered list of items in square brackets that can be referenced by an index. Because of the nature of GO, the way we deal with and access arrays is a bit different. In fact, we rarely deal with arrays directly in GO, but, instead, work with slices of the array. As stated in one of our lessons on GO arrays and slices from the Alta3 Research GO Essentials course: “arrays are very low-level memory management.”

A developer coming from a language such as those mentioned above will need to reshape their definition of a “slice” when programming in GO. A slice is actually more comparable to what we would have considered an array or a list in those languages, languages that also use the term “slice” in their own way. This is a prime (and somewhat extreme) example of one of the common challenges of programming: many terms are shared across languages and sometimes the definitions are very close but different enough to be confusing and send one scrambling for documentation.

In GO, an array is a value type, and a slice is a reference type to an array. Effectively, the slice is a pointer to a contiguous array, rather than a copy of an array. GO developers will almost always find themselves working through a slice rather than the underlying array. We can slice into an existing array or create a slice directly, which will create the underlying array for us.

An array in GO needs a length and a capacity, or, more simply, how many items are in it and how many items can be in it.

var myArray = [5]int{0,1,2,3,4} // essentially "create an array with a capacity of 5 that holds the following integers"

The line of code above creates an array of integers (int) with both a length and capacity of 5. GO is a typed language, so we are regularly establishing what type our data is, even the data that will be placed within the array. As is suggested in the sytnax above, we cannot mix types of data in an array the way we can in other languages. We can create some lightweight slices which point to this pre-existing array. The following syntax does resemble the methods in which we would slice into arrays in other languages.

var mySlice = myArray[1:3] // a slice that references a portion of the array above starting (inclusively) at index 1 through (exclusively) index3.
var mySlice2 = myArray[3:] // a slice that references a portion of the array above starting (inclusively) at index 3 through the end.

While creating slices “on the fly” without a pre-existing array is heavier on memory consumption, it is a common practice. We can do so with the built in make function, which automatically creates and refers to a zeroed array.

import "fmt"

func main() {
    mySlice3 := make([]int, 5) // make a slice with a length/capacity of 5
    fmt.Println(mySlice3)
}

The above will render [0 0 0 0 0], as GO will assign the value of 0 to undefined elements in the underlying array. Undefined booleans will be assigned a value of “false”, and unassigned strings will be assigned as an empty string.

Consider the following code:

var array = [4]int{1, 2, 3}
var slice = array[:]

func main() {
	fmt.Println(array)
	fmt.Println(slice)
}

Since we have created an array with a capacity of 4 but only assigned it 3 elements, the last item is zeroed out for both the array and the slice.

[1 2 3 0]
[1 2 3 0]

As in other languages, arrays and slices are mutable in GO. Let’s add a line to modify the slice:

var array = [4]int{1, 2, 3}
var slice = array[:] // slices the entire array

func main() {
  slice[3] = 4 // modify the slice
	fmt.Println(array)
	fmt.Println(slice)
}

Note that modifying the slice also modifies the underlying array:

[1 2 3 4]
[1 2 3 4]

Altering the underlying array will also affect the slice. Let’s change that line of code to do so:

var array = [4]int{1, 2, 3}
var slice = array[:]

func main() {
  array[3] = 4 // modify the array
	fmt.Println(array)
	fmt.Println(slice)
}

This produces the same output.

[1 2 3 4]
[1 2 3 4]

Both conceptually and practically, dealing with slices and arrays can be one of the more challenging aspects for a developer getting to know GO. But once you grasp it, you will have a fundamental understanding of what’s going on under the hood in GO. We’ve only really scratched the surface here. It would certainly be worth taking some time to continue to explore these concepts on the GO Playground or, better yet, enrolling in GO Essentials with Alta3 Research.