Signup/Sign In

Slices in Go

A Slice in Go is a segment of an array. It is used to wrap arrays to provide a more powerful, general interface to sequences of data.

As per the official documentation by Rob Pike, arrays are mainly used in transformation matrices, besides that most array programming in Go is done with slices rather than simple arrays.

Just like arrays, the slices in Go are indexable and have a length as well as a new parameter which is the capacity, but unlike arrays, they can be resized.

Internally, a slice is just a reference to an underlying array and if you assign one slice to another, then both the slices will refer to the same array.

If a function takes a slice argument then the changes that will be made to the elements of the slice will also be visible to the caller slice, which is nothing but the same as passing a pointer to the underlying array.

A slice doesn't store any data, it just describes a section of an underlying array.

The slice is represented using a three fields struct, where the first field is unsafe.Pointer to the underlying array, the second one being the length of the slice and the third is the capacity of the slice.

If we locate the file in the path /src/runtime/slice.go then we will notice the following struct shown below which is how a slice is internally declared in Go.

type slice struct{
     array unsafe.Pointer
     len int
     cap int
}

The fields in the struct are explained below:

  • unsafe.Pointer - the pointer is used to point to the first element of the array that is accessible through the slice.
  • len - the length is the total number of elements present in the array
  • cap - the capacity represents the maximum size up to which it can expand.

Declaring a slice in Go

In Go, we can declare a slice by following the syntax shown below.

var sl []T

// where sl is the name of the slice and T is the data type 

It should be noted that the slice declaration is almost similar to the declaration of an array, the only difference being is that we don't specify any size in the brackets as we do in the case of the arrays.

Consider the example shown below in which a slice is declared and then the fmt.Println() function is used to print all the elements of that slice.

package main
import (
	"fmt"
)
func main() {
	sl := []int{1, 2, 3}

	fmt.Println(sl)
}


[1 2 3]

Declaring a slice using the length in Go

In the example above, we declared a slice with the help of a slice literal, and then length wasn't provided as it will be automatically inferred, but in case we want to declare a slice with a specified beginning length then we can make use of the built-in make function that Go provides us with.

It should be noted that if we just specify the length of the slice, then the capacity of the slice will also be the same as the length.

Consider the example shown below where we are declaring a slice using the length.

package main

import (
	"fmt"
)

func main() {
	sl := make([]int, 3)
	
	fmt.Println(sl)
}


[0 0 0]

Declaring a slice using the length and Capacity

In the previous example, we learned how we can define the initial length of the slice and use it to declare a slice, now we will also make use of the capacity to declare a slice with initial capacity and length, and then we will print the slice.

Consider the example shown below:

package main

import (
	"fmt"
)

func main() {
	sl := make([]int, 3, 5)

	fmt.Println(sl)
	fmt.Println(len(sl))
	fmt.Println(cap(sl))
}


[0 0 0]
3
5

It is good to note that trying to create a slice with a capacity that's smaller than the length is not allowed.

Creating a slice with a slice literal in Go

A preferred way of creating a slice is to make use of a slice literal. It is similar to creating an array, except we don't specify a value within the [] operator.

The initial length and capacity are simply based on the number of elements that you have initialized.

Consider the example shown below where a slice literal is depicted

package main

import (
	"fmt"
)

func main() {
	sl := []string{"apple", "banana", "mango", "litchi"}

	fmt.Println(sl)
	fmt.Println(len(sl))
	fmt.Println(cap(sl))
}


[apple banana mango litchi]
4
4

Creating a slice from an array

We can also create a slice from an array as well, to do that we just need to specify two indexes low and high separated by a colon. See the syntax below.

sl[low:high]

//where low = starting index and high = ending index

The above expression selects a slice from an array. The resulting slice will include all the elements from the starting index to the ending index excluding the element at the ending index.

Consider the example shown below where we are creating slices from an array based on different values of low and high.

package main

import (
	"fmt"
)

func main() {
	arr := [5]string{"apple", "banana", "mango", "litchi"}
	sl := arr[1:3]
	fmt.Println("Array :", arr)
	fmt.Println("Slice :", sl)
}


Array : [apple banana mango litchi ]
Slice : [banana mango]

Slicing a Slice in Go

We can also re-slice a slice which will help in creating a new slice value that points to the same array.

Consider the example shown below where we are creating a new slice from a given slice.

package main

import (
	"fmt"
)

func main() {
	slOne := []string{"apple", "banana", "mango", "litchi"}

	slTwo := slOne[1:3]
	fmt.Println("Slice 1:", slOne)
	fmt.Println("Slice 2:", slTwo)
}


Slice 1: [apple banana mango litchi]
Slice 2: [banana mango]

Declaring a nil slice

A nil slice is a slice whose zero value is nil. It has a length and capacity equal to zero and has no underlying array in it.

Consider the example shown below where we create a nil slice and then print the length and capacity of the same.

package main

import (
	"fmt"
)

func main() {
	var sl []string
	fmt.Println("Slice:", sl)	
	fmt.Println(sl == nil)
	fmt.Println("Length:", len(sl))
	fmt.Println("Capacity:", cap(sl))
}


Slice: []
true
Length: 0
Capacity: 0

Declaring an Empty Slice

We can declare an empty slice with the help of the built-in make function.

Consider the example shown below where an empty slice is declared.

package main

import (
	"fmt"
)

func main() {
	sl := make([]int,0)
	fmt.Println("Slice:", sl)	
	fmt.Println(sl == nil)
	fmt.Println("Length:", len(sl))
	fmt.Println("Capacity:", cap(sl))
}


Slice: []
false
Length: 0
Capacity: 0

Appending to a slice in Go

It should be noted that if you try to do something like this, you will get a runtime error:

cities := []string{}
cities[0] = "New Delhi"

In the above example, you are trying to set a value to a referred array, but since the slice you defined is empty, which is just seating on top of the array, the array will also be empty and hence the slice won't be able to set a value.

One way is there which we can use to do that, and that way includes making use of the append function:

package main

import (
	"fmt"
)

func main() {
	cities := []string{}
	cities = append(cities, "New Delhi")
	fmt.Println(cities)
}


[New Delhi]

We can append more than one entry to a slice and the syntax remains the same as in the above example, just the number of elements in the arguments of the append() function will increase.

Consider the example shown below where we are appending multiple values to a slice.

package main

import (
	"fmt"
)
func main() {
	cities := []string{}
	cities = append(cities, "New Delhi","Mumbai","Barcelona")
	fmt.Println(cities)
}


[New Delhi Mumbai Barcelona]

Growing Slices in Go

One of the advantages of using a slice over an array is that it allows us to grow the capacity of the slice as much as we need.

Go takes care of all the operational details when you use the built-in append.

In case you want to make use of append, you will require a source slice and a value that you need to append. When the append function returns, it provides us with a new slice with the changes.

It is good to note that the append function always increases the length of the new slice to which you are appending.

It might so be the case that the capacity may or may not be affected, as it simply depends on the available capacity of the source slice.

Iterating over slices

We can iterate over the slices with the help of the for loop and since there are different variations possible of the for loop, we will consider all the approaches and we will also use the range clause to print the elements of a slice as well.

Consider the code shown below that depicts the different approaches with which we can traverse the elements of a slice.

package main

import (
	"fmt"
	"reflect"
)
func main() {
	arraY := []int{1, 2, 3, 4, 5}
	for i := 0; i < len(arraY); i++ {
		fmt.Print(arr[i], " ")
	}
	fmt.Println()
	var index int
	for index < len(arraY) {
		fmt.Print(arraY[index], " ")
		index++
	}
	fmt.Println()
	for _, val := range arraY {
		fmt.Print(val, " ")
	}
	fmt.Println()	
	fmt.Println(reflect.ValueOf(arraY))
}


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

Copy slices in Go

The copy() function is used to copy elements from one slice to another. Its signature looks like this:

func copy(dst,src []T) int

// where T is the data type, dst = destination slice and src = source slice

It takes two-slice as arguments, where the first slice is the destination slice and a source slice.

It then copied the elements from the source to the destination and returns the number of elements that are copied.

The number of elements that will be copied from one slice to another will be the minimum of the length of the source slice and the length of the destination slice.

Consider the example shown below where we are making use of the copy() function.

package main

import (
	"fmt"
)

func main() {
	sl := make([]int, 3)
	copiedSlice := copy(sl, []int{0, 1, 2, 3})
	fmt.Println(sopiedSlice)
	fmt.Println(sl)
}


3
[0 1 2]

Two Dimensional Slices in Go

As we already know that a multidimensional array is an array of arrays, similarly a multi-dimensional slice is a slice of slices.

To understand the idea behind this, let's first have a look at the definition of a slice. We know that a slice points to an underlying array which in turn is internally represented by a header.

A slice header is a struct that looks like this.

type SliceHeader struct{
     Data uintptr
     len int
     cap int
}

The Data field that you see in the slice struct is basically a pointer to the underlying array. For a one dimensional slice, we have the below declaration:

oneDSlice := make([]int,2)

The declaration of a two-dimensional slice would look something like this:

twoDSlice := make([][]int,2)

The above declaration means that we want to create a slice of 2 slices. We must understand this point.

It should be noted that in the above declaration we haven't specified the length of each of the inner 2 slices and we do that by explicitly initializing them like shown below:

for i := range twoDSlice{
    twoDSlice[i] = make([]int,3)
}

So by making use of the range clause, we specify the length of each of the two inner slices.

Another approach to do the same would be to something like this:

var twoDSlice = make([][]int,2)
twoDSlice[0] = []int{1,2,3}
twoDSlice[1] = []int{4,5,6}

Basically, we are creating a slice of 2*3 dimensions with the above declaration, which is nothing but a two-dimensional slice.

Conclusion

In the above article, we learned about what a slice is, following that we also learned how we can declare a slice, with length and with capacity also, then later we made use of the built-in make function to declare a slice.

Then we learned about how we can append values to a slice, following that we learned how we can copy one slice to another, and at last, we saw the two-dimensional slice.



About the author:
Pradeep has expertise in Linux, Go, Nginx, Apache, CyberSecurity, AppSec and various other technical areas. He has contributed to numerous publications and websites, providing his readers with insightful and informative content.