Arrays and Slices

Go has two collection types: Arrays and Slices.

You’re reading second article. Click here to read first article.

Arrays

Arrays in Go are fixed-size and contains values of the same type. To use an array, we must need to define both size and the type.

var x [n]type

Here, n is the size of the array x and type is one of the valid types in Go. For example:

var x [3]int

The x variable now can hold three integer values. By default, arrays are initialized with zero value of its type. So, the shape of the x should be [0 0 0].

For example:

package main

import "fmt"

func main() {
    var x [3]int

    fmt.Println(x)
}

Output is:

[0 0 0]

You can assign the value of an array by accessing the element of an array using index with box bracket or [].

x[0] = 2
x[1] = 3
x[2] = 4

For example:

package main

import "fmt"

func main() {
    var x [3]int

    x[0] = 2
    x[1] = 3
    x[2] = 4

    fmt.Println(x)
}

Output is:

[2 3 4]

Or you can do both - declaration and initialization in one line.

var x [3]int = [3]int{2, 3, 4}

For example:

package main

import "fmt"

func main() {
    var x [3]int = [3]int{2, 3, 4}
    
    fmt.Println(x)
}

Yes, we must need to specify the size and type on the right side as well. If an array is declared with the initial value, we can omit the size and type from the left side.

var x = [3]int{2, 3, 4}

For example:

package main

import "fmt"

func main() {
    var x = [3]int{2, 3, 4}
    
    fmt.Println(x)
}

Or by using the := operator, we can remove the var keyword.

x := [3]int{2, 3, 4}

For example:

package main

import "fmt"

func main() {
    x := [3]int{2, 3, 4}
    
    fmt.Println(x)
}

Go provides the built-in len() function to get the length of an array.

len(x)

For example:

package main

import "fmt"

func main() {
    x := [3]int{2, 3, 4}
    
    fmt.Println(len(x))
}

Output is:

3

Arrays are assignable, meaning that you can assign one array to another:

x := [3]int{2, 3, 4}
y := x

For example:

package main

import "fmt"

func main() {
    x := [3]int{2, 3, 4}
    
    y := x
    
    fmt.Println(x)
    fmt.Println(y)
}

Output is:

[2, 3, 4]
[2, 3, 4]

When assigned, arrays are copied. So, modifying one does not affect the other.

For example:

package main

import "fmt"

func main() {
    x := [3]int{2, 3, 4}
    y := x

    x[0] = 0
    x[1] = 0
    x[2] = 0
    
    fmt.Println(x)
    fmt.Println(y)
}

Output is:

[0 0 0]
[2 3 4]

We can compare the array using ==. If all the elements in both arrays are same, the comparison returns true; otherwise, it returns false. For example,

package main

import "fmt"

func main() {
    x := [3]int{2, 3, 4}
    y := [3]int{2, 2, 4}
    z := [3]int{2, 3, 5}
    
    y[1] = 3
    z[2] = 4
    
    fmt.Println(x == y)
    fmt.Println(y == z)
    fmt.Println(x == z)
}

Output is:

true
true
true

As array are fixed-size, there are only limited operations one can do. For example, you can’t do push or pop or append element from/to array. Due to this, most of the time, slices are used instead of an array which we will cover in very next section. But, there is one more function which we can use with an array and that is - cap() function for capacity to hold element in array. But, it returns the same values as len() return because of the fixed-size.

Slices

Slices are arrays but can grow or shrink as needed. Defining slice is almost similar to array except due to grow and shrink capabilities, we do not have to define the size of slice.

var x []type

The syntax difference between array and slice is - empty bracket without size.

For example,

var x []int

The underlying structure of the slice is a pointer to an array. Due to this, initial value of slice x is nil instead of zero value because memory is not allocated to this slice.

For example:

package main

import "fmt"

func main() {
	var x []int

	fmt.Println(x)
}

Output is:

[]

Although, you’re seeing an empty slice but it is not. Let’s compare it with nil and confirm.

package main

import "fmt"

func main() {
	var x []int

	fmt.Println(x)

	fmt.Println(x == nil)
}

Output is:

[]
true

In order to get the empty slice, you literally have to define a slice with empty value.

var x []int = []int{}

This is the empty slice and not nil one.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{}

	fmt.Println(x)

	fmt.Println(x == nil)
}

Output is:

[]
false

We can check the length of the slice using len() function and capacity to hold elements in slice using cap() function.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{}

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

As slice is initialized with empty slice, both length and capacity is 0. But, we can add the element to the slice x using append() function. append() is a built-in function that can append an element to the slice. append() function accepts two arguments - slice and element that we want to append in the slice.

x = append(x, 2)

Here, we are adding element 2 in slice and saving the result of append() which is slice into x.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{}

	x = append(x, 2)

	fmt.Println(x)
}

Output is:

[2]

We can add as many elements as we want to this slice using the same append() function.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{}

	x = append(x, 2)
	x = append(x, 3)
	x = append(x, 4)
	x = append(x, 5)

	fmt.Println(x)
}

Output is:

[2 3 4 5]

When working with slice, you don’t have to start with empty slice. If you have the initial values, you can assign it without mentioning the size.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{2, 3, 4, 5}

	fmt.Println(x)
}

Output is:

[2 3 4 5]

Variable syntax shortcuts are available for slice as well. So,

package main

import "fmt"

func main() {
	var x = []int{2, 3, 4, 5}

	fmt.Println(x)
}

works and so as :=.

package main

import "fmt"

func main() {
	x := []int{2, 3, 4, 5}

	fmt.Println(x)
}

Onward now, I’ll use this shortcut syntax unless other syntax is specifically needed.

You can access and modify the existing element from the slice using the index in box bracket.

For example:

package main

import "fmt"

func main() {
	var x []int = []int{2, 3, 4, 5}

	x[0] = 0
	x[1] = -1
	x[3] = -3

	fmt.Println(x)
}

As slice is a pointer to underlying array, if we create a new slice from existing one by assignment, changing value in one slice affects another.

For example:

x := []int{2, 3, 4, 5}
y := x

x[0] = -1

The first element in x and y slices is now -1 as because both slices are pointing to same underlying array.

For example:

package main

import "fmt"

func main() {
	x := []int{2, 3, 4, 5}
	y := x

	x[0] = -1

	fmt.Println(x)
	fmt.Println(y)
}

Output is:

[-1 3 4 5]
[-1 3 4 5]

But, if you want to make an independent slice from the original slice, we need to use copy() function. The copy() function accepts two arguments - destination slice where elements will be copied and source slice from which elements will be copied. That means we first need to create destination slice with the same type and size of the slice we want to copy from. We can use make() function to create a new slice with type and initial size. make() accepts two arguments - type and size. We can get the size of slice x using len(x). The value make() function return can be saved into the variable.

y := make([]int, len(x))

Now, we can use copy() function.

copy(y, x)

For example:

package main

import "fmt"

func main() {
	x := []int{2, 3, 4, 5}
	y := make([]int, len(x))

	copy(y, x)

	fmt.Println(x)
	fmt.Println(y)
}

Output is:

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

Changing a value in x or y should not affect each other.

For example:

package main

import "fmt"

func main() {
	x := []int{2, 3, 4, 5}
	y := make([]int, len(x))

	copy(y, x)

	x[0] = 42
	y[3] = 42

	fmt.Println(x)
	fmt.Println(y)
}

Output is:

[42 3 4 5]
[2 3 4 42]