Learn Go: An Introduction to Slices in Golang #18

A slice is a data type in Go that is a mutable, since the size of a slice is variable, there is a lot more flexibility

Gopher

Lets go!!

Published · Jan 27 2020

Slices wrap arrays to give a more general, powerful, and convenient interface to sequences of data.

Except for items with explicit dimension such as transformation matrices, most array programming in Go is done with slices rather than simple arrays.


Slices

A slice is a data type in Go that is a mutable, or changeable, ordered sequence of elements. Since the size of a slice is variable, there is a lot more flexibility when using them; when working with data collections that may need to expand or contract in the future, using a slice will ensure that your code does not run into errors when trying to manipulate the length of the collection.

In most cases, this mutability is worth the possible memory re-allocation sometimes required by slices when compared to arrays. When you need to store a lot of elements or iterate over elements and you want to be able to readily modify those elements, you’ll likely want to work with the slice data type.
. . .

Defining Slices

A slice is a segment of an array. Like arrays slices are indexable and have a length. Unlike arrays this length is allowed to change. Here's an example of a slice:
var x []float64

The only difference between this and an array is the missing length between the brackets. In this case x has been created with a length of 0.

If you want to create a slice you should use the built-in make function:
x := make([]float64, 5)

This creates a slice that is associated with an underlying float64 array of length 5. Slices are always associated with some array, and although they can never be longer than the array, they can be smaller. The make function also allows a 3rd parameter:
x := make([]float64, 5, 10)

10 represents the capacity of the underlying array which the slice points to:
Lets create a slice :
package main

import "fmt"

func main() {
seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
fmt.Printf("%q\n", seaCreatures)
}


In the above program in line no. 6 , seaCreatures creates an array with 5 strings and returns a slice reference which is stored in seaCreatures.
. . .

Slicing Arrays into Slices

By using index numbers to determine beginning and endpoints, you can call a subsection of the values within an array. This is called slicing the array, and you can do this by creating a range of index numbers separated by a colon, in the form of [first_index:second_index]. It is important to note however, that when slicing an array, the result is a slice, not an array.

Let’s say you would like to just print the middle items of the seaCreatures array, without the first and last element. You can do this by creating a slice starting at index 1 and ending just before index 4:
package main

import "fmt"

func main() {
seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
fmt.Printf("%q\n", seaCreatures[1:4])
}

Running a program with this line would yield the following:
Output [cuttlefish squid mantis shrimp]

When creating a slice, as in [1:4], the first number is where the slice starts (inclusive), and the second number is the sum of the first number and the total number of elements you would like to retrieve:
array[starting_index : (starting_index + length_of_slice)]

In this instance, you called the second element (or index 1) as the starting point, and called two elements in total. This is how the calculation would look:
array[1 : (1 + 2)]

Which is how you arrived at this notation:
seaCreatures[1:3]

If you want to set the beginning or end of the array as a starting or end point of the slice, you can omit one of the numbers in the array[first_index:second_index] syntax. For example, if you want to print the first three items of the array coral — which would be "blue coral", "foliose coral", and "pillar coral" — you can do so by typing:
package main

import "fmt"

func main() {
seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
fmt.Println(seaCreatures[:3])
}
Output [shark cuttlefish squid]

This printed the beginning of the array, stopping right before index 3.
To include all the items at the end of an array, you would reverse the syntax:
package main

import "fmt"

func main() {
seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp", "anemone"}
fmt.Println(seaCreatures[1:])
}


This would give the following slice:
Output [cuttlefish squid mantis shrimp anemone]

This section discussed calling individual parts of an array by slicing out subsections. Next, you’ll learn how to use slicing to convert entire arrays into slices.
. . .

Appending to a slice

As we already know arrays are restricted to fixed length and their length cannot be increased. Slices are dynamic and new elements can be appended to the slice using append function. The definition of append function is func append(s []T, x ...T) []T.

x ...T in the function definition means that the function accepts variable number of arguments for the parameter x. These type of functions are called variadic functions.

One question might be bothering you though. If slices are backed by arrays and arrays themselves are of fixed length then how come a slice is of dynamic length. Well what happens under the hood is, when new elements are appended to the slice, a new array is created.

The elements of the existing array are copied to this new array and a new slice reference for this new array is returned. The capacity of the new slice is now twice that of the old slice. Pretty cool right :). The following program will make things clear.
package main import ( "fmt" ) func main() { cars := []string{"Ferrari", "Honda", "Ford"} fmt.Println("cars:", cars, "has old length", len(cars), "and capacity", cap(cars)) //capacity of cars is 3 cars = append(cars, "Toyota") fmt.Println("cars:", cars, "has new length", len(cars), "and capacity", cap(cars)) //capacity of cars is doubled to 6 }


In the above program, the capacity of cars is 3 initially. We append a new element to cars in line no. 10 and assign the slice returned by append(cars, "Toyota") to cars again. Now the capacity of cars is doubled and becomes 6
cars: [Ferrari Honda Ford] has old length 3 and capacity 3 cars: [Ferrari Honda Ford Toyota] has new length 4 and capacity 6
. . .

Removing an Element from a Slice

Unlike other languages, Go does not provide any built-in functions to remove an element from a slice. Items need to be removed from a slice by slicing them out.

To remove an element, you must slice out the items before that element, slice out the items after that element, then append these two new slices together without the element that you wanted to remove.

If i is the index of the element to be removed, then the format of this process would look like the following:
slice = append(slice[:i], slice[i+1:]...)

From coralSlice, let’s remove the item "elkhorn coral". This item is located at the index position of 3.
package main

import "fmt"

func main() {
coralSlice := []string{"blue coral", "foliose coral", "pillar coral", "elkhorn coral", "black coral", "antipathes", "leptopsammia", "massive coral", "soft coral"}

coralSlice = append(coralSlice[:3], coralSlice[4:]...)

fmt.Printf("%q\n", coralSlice)
}


Now the element at index position 3, the string "elkhorn coral" is no longer in our slice coralSlice.

["blue coral" "foliose coral" "pillar coral" "black coral" "antipathes" "leptopsammia" "massive coral" "soft coral"]
. . .

length and capacity of a slice

The length of the slice is the number of elements in the slice. The capacity of the slice is the number of elements in the underlying array starting from the index from which the slice is created.

Since slices have a variable length, the len()method is not the best option to determine the size of this data type.

Instead, you can use the cap() function to learn the capacity of a slice. This will show you how many elements a slice can hold, which is determined by how much memory has already been allocated for the slice.

Lets write some code to understand this better.
package main import ( "fmt" ) func main() { fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"} fruitslice := fruitarray[1:3] fmt.Printf("length of slice %d capacity %d", len(fruitslice), cap(fruitslice)) //length of fruitslice is 2 and capacity is 6 }


In the above program, fruitslice is created from indexes 1 and 2 of the fruitarray. Hence the length of fruitslice is 2.

The length of the fruitarray is 7. fruiteslice is created from index 1 of fruitarray.

Hence the capacity of fruitslice is the no of elements in fruitarray starting from index 1 i.e from orange and that value is 6. Hence the capacity of fruitslice is 6. The program outputs length of slice 2 capacity 6.

. . .

Multidimensional Slices

You can also define slices that consist of other slices as elements, with each bracketed list enclosed inside the larger brackets of the parent slice. Collections of slices like these are called multidimensional slices. T

hese can be thought of as depicting multidimensional coordinates; for example, a collection of five slices that are each six elements long could represent a two-dimensional grid with a horizontal length of five and a vertical height of six.

Let’s examine the following multidimensional slice:
package main

import "fmt"

func main() {
seaNames := [][]string{{"shark", "octopus", "squid", "mantis shrimp"}, {"Sammy", "Jesse", "Drew", "Jamie"}}

fmt.Println(seaNames[1][0])
fmt.Println(seaNames[0][0])
}

To access an element within this slice, we will have to use multiple indices, one for each dimension of the construct:
fmt.Println(seaNames[1][0]) fmt.Println(seaNames[0][0])

In the preceding code, we first identify the element at index 0 of the slice at index 1, then we indicate the element at index 0 of the slice at index 0. This will yield the following:
Output:
[[shark octopus squid mantis shrimp] [Sammy Jesse Drew Jamie]] Sammy shark

The following are the index values for the rest of the individual elements:
seaNames[0][0] = "shark" seaNames[0][1] = "octopus" seaNames[0][2] = "squid" seaNames[0][3] = "mantis shrimp" seaNames[1][0] = "Sammy" seaNames[1][1] = "Jesse" seaNames[1][2] = "Drew" seaNames[1][3] = "Jamie"

When working with multidimensional slices, it is important to keep in mind that you’ll need to refer to more than one index number in order to access specific elements within the relevant nested slice.

. . .

Conclusion

In this tutorial, you learned the foundations of working with slices in Go. You went through multiple exercises to demonstrate how arrays are fixed in length, whereas slices are variable in length, and discovered how this difference affects the situational uses of these data structures.
. . .
Next Tutorial: Understanding Maps in Go #19

. . .
References:
[ 2 ] Slices

On a mission to build Next-Gen Community Platform for Developers