Search⌘ K
AI Features

Slices and Arrays

Explore how Go's slices and arrays function, their differences in memory handling, and why slices are preferred for flexible data management. Understand how passing slices affects underlying data and how to avoid common pitfalls with slicing.

We'll cover the following...

In Go, slices and arrays serve a similar purpose. They are declared nearly the same way:

Go (1.16.5)
package main
import "fmt"
func main() {
slice := []int{1, 2, 3}
array := [3]int{1, 2, 3}
// let the compiler work out array length
// this will be an equivalent of [3]int
array2 := [...]int{1, 2, 3}
fmt.Println(slice, array, array2)
}

Slices feel like arrays with useful functionality on top. They use pointers to arrays internally in their implementation. Slices however are so much more convenient that arrays are rarely used directly in Go.

Arrays

An array is a typed sequence of memory of a fixed length. Arrays of different lengths are considered to be different incompatible types. Unlike in C, array elements are initialized to zero values when an array is created so there’s no need to do that explicitly.

Also unlike in C, a Go array is a value type. It’s not a pointer to the first element of a block of memory. If you pass an array into a function, the whole array will be copied. You can still pass a pointer to an array to not have it copied.

Slices

A slice is a descriptor of an array segment. It’s a very useful data structure, but perhaps slightly unusual. There are several ways to shoot yourself in a foot with it, all of which can be avoided if you know how a slice works internally. Here’s the actual definition of a slice in Go source code:

Go (1.16.5)
package main
import "unsafe"
func main() {
type slice struct {
array unsafe.Pointer
len int
cap int
}
}

This has interesting implications. A slice itself is a value type, but it references the array it uses with a pointer. Unlike with an array, if you pass a slice to a function you would get a copy of array pointer, len, and cap properties (the first block in the image above), but the data in the array itself wouldn’t be copied. Both copies of the slice would point to the same array. The same thing happens when you “slice” a slice. Slicing creates a new slice, which still points to the same array:

Go (1.16.5)
package main
import "fmt"
func f1(s []int) {
// slicing the slice creates a new slice but does not copy the array data
s = s[2:4]
// modifying the sub-slice changes the array of slice in main function as well
for i := range s {
s[i] += 10
}
fmt.Println("f1", s, len(s), cap(s))
}
func main() {
s := []int{1, 2, 3, 4, 5}
// passing a slice as an argument makes a copy of the slice properties (pointer, len and cap)
// but the copy shares the same array
f1(s)
fmt.Println("main", s, len(s), cap(s))
}

If you’re unaware of what a slice is, you might assume that it’s a value type, and be surprised that f1 “corrupted” the data in the slice in main.