In this tutorial, we will learn about slices in Golang using 14 easy examples. In Go, both array and slices are used to store elements based on different scenarios. Array has certain limitations like:
- Array has fixed size
- Array size must be specified in the declaration
- Array holds the actual elements and so on
Because of these limitations of array, we use more powerful and dynamic data structure called slices in Golang. Let us understand what slices are and what all operations we can do using slices with examples in the next section.
Slices Overview
Slices are fundamental data structure in Golang that provides a more flexible way to work with variable length of sequences of elements. Slices stores only similar type of elements. Working with slices looks quite a bit like working with arrays, but there are subtle differences. The first thing to notice is that we do not specify the size of the slice when we declare it unlike array, where we specify the size when declaring the array. Syntax for slices:
var x []int
This syntax creates a slice of integers. ‘[]’ denotes that the size of the slice is dynamic and keep on expanding as we go on adding the elements to the slice. Since no value is assigned, x is assigned the zero value for a slice. This is called nil slice. In Go, nil is not same as null. nil is an identifier that represents the lack of a value for some types. nil has no type, so it can be assigned to or compared against values of different types.
var x = []int{<elem1>, <elem2>, <elem3>}
This syntax creates a slice of 3 integers.
NOTE:
Slices in Golang: [14 Easy Examples]
Also read: Go Run vs Go Build: Explained with Example
Creating Slices
There are multiple ways to create a slice. Two most common ways are either to use var keyword or slice literal.
var x = []int{50, 60, 80}
Example-1: This creates a slice of 3 integer using a slice literal. Just like array, we can also specify only the indices with values in the slice literal.
package main import ( "fmt" ) func main() { var x = []int{50, 60, 80} fmt.Println(x) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\create-slice.go [50 60 80]
x := []int{1, 4: 110, 6, 8: 50, 10}
Example-2: This creates a slice of 12 integers using var keyword with the following values: [1, 0, 0, 0, 110, 6, 0, 0, 50, 10]
package main import ( "fmt" ) func main() { var x = []int{1, 4: 110, 6, 8: 50, 10} fmt.Println(x) }
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\create-slice.go [1 0 0 0 110 6 0 0 50 10]
NOTE:
Read and Write Slices
We read and write slices using bracket syntax as shown below. Just like array, we can not read or write past the end or use a negative index.
Example-3: In below example, we have created a slice from which we are reading the value at index 2 and writing the value at the end of the slice.
package main import ( "fmt" ) func main() { var x = []int{1, 4: 110, 6, 8: 50, 10} fmt.Println("Reading from the Slice: ", x[2]) y := append(x, 90) fmt.Println("Writing to the Slice: ", y) }
OUTPUT
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\read-write-slice.go Reading from the Slice: 0 Writing to the Slice: [1 0 0 0 110 6 0 0 50 10 90]
Example-4: If we try to read from the slice using negative index, we will get below error.
package main import ( "fmt" ) func main() { var x = []int{1, 4: 110, 6, 8: 50, 10} fmt.Println("Reading from the Slice: ", x[-1]) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\read-write-slice.go # command-line-arguments .\read-write-slice.go:9:44: invalid argument: index -1 (constant of type int) must not be negative
Length of Slices
We use built-in function called len to find out the length of slices, similar to array. Note that when we pass nil slice to len function, it returns the size as zero.
Example-5: In the below example, we have created a slice and priting its length using len function.
package main import ( "fmt" ) func main() { var x = []int{1, 4: 110, 6, 8: 50, 10} fmt.Println("Size of slice: ", len(x)) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-length.go Size of slice: 10
Capacity of Slices
Also read: CPU Bound Processes in Golang [4 Best Examples]
In Go, each element of slice is assigned to consecutive memory locations which makes it quick to read and write these values. Capacity is defined as the number of consecutive memory location reserved for a slice. This can be larger than the length of slice. When we append additional elements to a slice, the moment length becomes equal to the capacity of slice , there is no room left for any additional elements.
In such case, Go runtime allocates a new slice with a large capacity and values in original slice are copied to the new slice. The new elements are now copied to the end of new slice and new slice is returned. The capacity of a slice is find out using built-in function called cap.
Example-6: In below example, notice how the capacity of slice is getting expanded when we keep on adding the elements to it.
package main import ( "fmt" ) func main() { var x = []int{} fmt.Println(x, len(x), cap(x)) x = append(x, 10) fmt.Println(x, len(x), cap(x)) x = append(x, 12, 30) fmt.Println(x, len(x), cap(x)) x = append(x, 9) fmt.Println(x, len(x), cap(x)) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-capacity.go [] 0 0 [10] 1 1 [10 12 30] 3 3 [10 12 30 9] 4 6
We can also create a slice of definite capacity using a built-in function called make. Example:
- var x = make([]int, 10)
- var x = make([]int, 5 , 10)
First option creates a slice of integers having length and capacity both 10. Second option again creates a slice of integers but here the length is 5 and capacity is 10.
Appending to Slices
To grow the length of slices, we use built-in function called append. The append function takes at least two parameters, a slice of any type and a value of that type. It returns the slice of the same type.
Example-7: In the below example, we are adding two elements (9,10) at the end of slice x.
package main import ( "fmt" ) func main() { var x = []int{4, 5, 2, 8} x = append(x, 9, 10) fmt.Println("New Slice after append: ", x) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-append.go New Slice after append: [4 5 2 8 9 10]
We can also append one slice onto another by using the … operator.
Example-8: In this example, we are adding slice y at the end of slice x.
package main import ( "fmt" ) func main() { var x = []int{20, 30} var y = []int{80, 90, 10} x = append(x, y...) fmt.Println("New Slice after append: ", x) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-append.go New Slice after append: [20 30 80 90 10]
Slicing Slices
A slice expression creates a slice from a slice. It is written inside brackets and consists of a starting offset and an ending offset, separated by a (:) just like we do string slices in Python.
Example-9: In below example, we are slicing the slice x in different ways.
package main import ( "fmt" ) func main() { x := []int{10, 20, 30, 40, 50} a := x[:2] b := x[1:] c := x[1:3] d := x[:] fmt.Println("a: ", a) fmt.Println("b: ", b) fmt.Println("c: ", c) fmt.Println("d: ", d) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-slicing.go a: [10 20] b: [20 30 40 50] c: [20 30] d: [10 20 30 40 50]
Copying Slices
To copy one slice to another, we use a built-in function called copy. It takes two parameters, first is destination slice and the second is the source slice. It is not necessary to always copy the entire slice, we can copy few elements also to destination slice.
Example-10: In the below example, we have created slice x of 3 integers and slice y of type integers whose length is 4 and capacity is 5. In the output, it returns total number of elements copied which is 3 in this case along with new slice after copy.
Copy entire slice
package main import ( "fmt" ) func main() { x := []int{10, 20, 30} y := make([]int, 4, 5) num := copy(y, x) fmt.Println("Number of elements copied:", num) fmt.Println("New slice after copy:", y) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-copy.go Number of elements copied: 3 New slice after copy: [10 20 30 0]
Copy middle elements from the source slice
Example-11: In below example, we are copying elements at index 2 & 3 of slice x to slice y.
package main import ( "fmt" ) func main() { x := []int{10, 20, 30, 40, 12, 23, 13} y := make([]int, 4, 5) num := copy(y, x[2:4]) fmt.Println("Number of elements copied: ", num) fmt.Println("New slice after copy: ", y) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-copy.go Number of elements copied: 2 New slice after copy: [30 40 0 0]
Iterating Slices
We can use for-range loop like any other programming language to iterate over the slices.
Example-12: In below example, we have created integer slice x which has 5 elements. We iterate over this slice and prints its elements using for-range loop.
package main import "fmt" func main() { x := []int{10, 20, 30, 40, 50} for index, elem := range x { fmt.Printf("Index: %d , Element: %d\n", index, elem) } }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-iterate.go Index: 0 , Element: 10 Index: 1 , Element: 20 Index: 2 , Element: 30 Index: 3 , Element: 40 Index: 4 , Element: 50
Passing Slices to Functions
Slices are passed by value, but the value being passed is a reference to the underlying array.
Example-13: In below example, we are passing slice x to the function modifySlice and replacing the element at index zero to value 100.
package main import ( "fmt" ) func modifySlice(s []int) { s[0] = 100 } func main() { var x = []int{10, 20, 30} modifySlice(slice) fmt.Println(slice) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\slice-func.go [100 20 30]
Multidimensional Slices
When we create a slice whose elements too are slices, it is known as multidimensional slices.
Example-14: In below example, we have created multi dimensional slice of type integer having two elements.
package main import "fmt" func main() { x := [][]int{ {1, 2, 3}, {4, 5, 6}, } fmt.Println("Multidimensional Slice", x) }
PS C:\Users\linuxnasa\OneDrive\Desktop\Go-Dump> go run .\multi-dim-slice.go Multidimensional Slice [[1 2 3] [4 5 6]]
Summary
Slices are essential data structure in Golang due to its flexibility and memory efficiency. They provide a versatile way to work with collections of data while maintaining the benefits of arrays.