mdawar.dev

A blog about programming, Web development, Open Source, Linux and DevOps.

Go - Slices

  • Slices are built on top of arrays that hold their elements
  • Slices have dynamic sizes, elements can be added or removed as needed
  • A slice literal is declared just like an array literal but without the length

Declaration

go
// A slice is declared like an array literal but without the length.
var numbers []int  // Slice of integers

var names []string // Slice of strings

var multi [][]int  // Slice of slices of integers

Initialization

A slice is associated with an undelying array that hold its elements, the slice shares storage with its array and any other slices of the same array.

A slice can be initialized using the slice literal syntax that describes the entire underlying array.

go
// Initialize using the slice literal syntax.
// A new hidden array is allocated that holds the slice elements.
numbers := []int{1, 2, 3, 4}
letters := []string{"a", "b", "c"}

// Initialize a multidimentional slice (slice of slices).
multi := [][]int{
  []int{1, 2, 3},
  []int{4, 5, 6},
}

// The type can be omitted for the elements.
multi = [][]int{
  {1, 2, 3},
  {4, 5, 6},
}

The slice literal syntax is a shorthand for a slice operation applied to and array.

A slice expression creates a slice from an array:

  • Syntax: a[low:high] (a can be an array or a pointer to an array)
  • The low and high indices select which elements appear in the result
  • Any of the indices may be omitted, a missing low index defaults to zero, a missing high index defaults to the length of the sliced array
go
// A slice literal.
numbers := []int{1, 2, 3, 4}

// A shorthand for creating a slice from an array.
array := [4]int{1, 2, 3, 4} // Array that holds the elements
// Both indices are omitted to refer to the whole array.
numbers = array[:] // Slice that refers to the array

// Same as
numbers = array[0:len(array)]
numbers = array[:len(array)]  // The low index defaults to 0
numbers = array[0:]           // The high index defaults to the length

We can also create a new slice that refers to a subrange of an existing slice.

go
slice := []int{1, 2, 3, 4} // [1 2 3 4]

// Create a new slice with elements from index 1 (inclusive) to 3 (exclusive).
subSlice := slice[1:3] // [2 3]

Slices can be created using the make() function where the type, length and optional capacity may be specified make([]T, length, capacity).

go
// Initialize a slice using the `make` function.
// The elements will be initialized to the zero value of their type.
// The second argument is the length of the slice.
slice := make([]int, 4) // []int{0, 0, 0, 0}, len=4, cap=4

// Equivalent to allocating an array and slicing it.
slice = new([4]int)[:] // []int{0, 0, 0, 0}, len=4, cap=4

// The third and optional argument is the capacity of the slice.
// It defines the size of the underlying array.
slice = make([]int, 4, 10) // []int{0, 0, 0, 0}, len=4, cap=10

// Equivalent to
slice = new([10]int)[0:4] // []int{0, 0, 0, 0}, len=4, cap=10

// Empty (non-nil) slice.
empty := make([]int, 0) // []int{}, len=0, cap=0

For slices of slices, the inner slices must be initialized individually.

go
slice := make([][]int, 2) // [][]int{[]int(nil), []int(nil)}

slice[0] // nil
slice[1] // nil

slice[0] = make([]int, 4) // []int{0, 0, 0, 0}
slice[1] = make([]int, 4) // []int{0, 0, 0, 0}

Length and Capacity

The len() function can be used to get the length of a slice and the cap() function to get the capacity.

go
// A slice has a length and a capacity.
// 0 <= len(slice) <= cap(slice)
slice := []int{1, 2, 3, 4, 5, 6}

// The length of a slice is the number of elements it contains.
length := len(slice)   // 6

// The underlying array of a slice may extend past the end of the slice.
// The capacity is a measure of that extent, it is the sum of the length
// of the slice and the length of the array beyond the slice.
capacity := cap(slice) // 6

s1 := slice[:0]
len(s1) // 0
cap(s1) // 6

// A slice's length can be extended by re-slicing it.
s1 = s1[:4]
len(s1) // 4
cap(s1) // 6

s2 := s[:3]
len(s2) // 3
cap(s2) // 6

s3 := slice[2:]
len(s3) // 2
cap(s3) // 4 (Starting from the element at index 2)

Zero Value

The value of an uninitialized slice is nil and it has no underlying array.

go
var slice []int // nil

// The length and capacity of a nil slice are 0.
len(slice) // 0
cap(slice) // 0

// Indexing a nil slice will cause a panic.
slice[0] // panic: runtime error: index out of range

Slice Elements

The slice elements can be accessed using their index in square brackets []:

go
first := slice[0]           // Access the 1st element

last := slice[len(slice)-1] // Access the last element

n := numbers[0][1]          // Access the 2nd element of the 1st slice

slice[1] = 10               // Set the value of the 2nd element

numbers[1][0] = 7           // Set the value of the 1st element of the 2nd slice

// A panic will occur at runtime if the index is out of range.
slice[100] // panic: runtime error: index out of range

// Pointer to a slice.
p := &slice

// Explicit pointer dereferencing is required not like arrays.
(*p)[0] = 10

p[0] = 10 // invalid operation: cannot index p

Changing the elements of a slice modifies the underlying array, as they both share the same memory storage.

go
array := [4]int{1, 2, 3, 4} // Array that holds the elements
slice := array[:]           // Slice that refers to the whole array

// Changing the elements of a slice modifies the corresponding
// elements of its underlying array as the slice and the array
// share the same memory storage.
slice[0] = 11

// The same goes for modifying the array elements directly.
array[3] = 44

fmt.Println(array) // [11 2 3 44]
fmt.Println(slice) // [11 2 3 44]

When a slice is passed to a function as an argument, a reference to the undelying array is passed and not a copy.

go
slice := []int{1, 2, 3} // [1 2 3]

// Changes made to the slice inside the function are made on
// the original array and will be visible outside of the function.
func setFirstElement(s []int, n int) {
  s[0] = n
}

setFirstElement(slice, 10)

fmt.Println(slice) // [10 2 3]

Append Elements

The built-in append() function is used to append one or more values to a slice and returns the resulting slice.

go
var s []int // nil, len=0, cap=0

// It works on nil slices.
s = append(s, 1) // []int{1}, len=1, cap=1

// If the capacity of the slice is not large enough to fit the additional
// values, a new larger array will be allocated to fit all the values
// otherwise append re-uses the underlying array.
// That's why we assign the resulting slice to the same variable.
s = append(s, 2) // []int{1, 2}, len=2, cap=2

// Append multiple values at the same time.
s = append(s, 3, 4, 5) // []int{1, 2, 3, 4, 5}, len=5, cap=6

Copy a Slice

The copy() function copies elements from a source slice to a destination slice and returns the number of copied elements.

go
dest := make([]int, 5) // []int{0, 0, 0, 0, 0}
src := []int{1, 2, 3, 4, 5}

// Copy the elements from `src` to `dest`.
// Both arguments must be slices of the same element type.
n := copy(dest, src) // Returns the number of copied element

fmt.Println(n)    // 5
fmt.Println(dest) // [1 2 3 4 5]

// `copy` can handle source and destination slices that share the same
// underlying array, handling overlapping slices correctly.
n = copy(dest, dest[3:]) // copy([1 2 3 4 5], [4 5])

// The number of elements copied is the minimum of the lengths of the 2 slices.
// In this case the source slice is of length 2, so 2 elements are copied.
fmt.Println(n)    // 2
fmt.Println(dest) // [4 5 3 4 5]

// A special case exists when copying to a `[]byte` destination,
// the source argument can also be of type string.
bytes := make([]byte, 2) // []byte{0x0, 0x0}
n = copy(bytes, "Go")

fmt.Println(n)             // 2
fmt.Println(string(bytes)) // Go

Iteration

We can iterate over a slice using the for range loop.

go
s := []string{"a", "b", "c"}

// Iterate over a slice.
for i, v := range s {
  // i: Index of the element.
  // v: A copy of element at this index.
  fmt.Println(i, v)
}

// If we only need the first item (index) we can omit the second.
for i := range s {
  fmt.Println(i)
}

// If we only need the value we can use the blank identifier `_`
// to discard the index.
for _, v := range s {
  fmt.Println(v)
}