Slice expressions in Go

There are many great introductions to slices in Golang:

This story focuses exclusively on slice expressions. They can create two types of values:

  • substring from string operand
  • slice from array, pointer to array or slice itself

Language specification defines two types of slice expressions — simple and full…

Simple slice expressions

The most known form of slice expression is:

input[low:high]

Indices low and high must be integers. They specify which elements of the operand (input) are placed inside the resulting slice or string. The result contains operand’s elements starting at low (inclusively) up to high (exclusively). Operand is either string, array, pointer to array or slice (playground):

fmt.Println("foobar"[1:3]) // "oo"
numbers := [5]int{1, 2, 3, 4, 5}
fmt.Println(numbers[1:3]) // [2, 3]
Length of the the result is high — low

Applying slice expression to array’s pointer is as shorthand for first dereferencing such pointer and then applying slice expression in a regular manner (playground):

numbers := [5]int{1, 2, 3, 4, 5}
fmt.Println((&numbers)[1:3]) // [2, 3]

Indices low or high can be omitted. Default values are then used. For low it’s 0 and for high it’s the length of the operand (playground):

fmt.Println("foo"[:2]) // "fo"
fmt.Println("foo"[1:]) // "oo"
fmt.Println("foo"[:]) // "foo"

Indices cannot be arbitrary numbers (playground):

  • negative numbers aren’t allowed
  • lowhigh
  • highlen(input)
//fmt.Println("foo"[-1:]) // invalid slice index -1 (index must be non-negative)
//fmt.Println("foo"[:4]) // invalid slice index 4 (out of bounds for 3-byte string)
fmt.Println("foo"[2:2]) // ""(blank)
//fmt.Println("foo"[2:1]) // invalid slice index: 2 > 1

If out of range indices cannot be detected while compilation then runtime panic occurs (playground):

func low() int {
return 4
}
func main() {
fmt.Println("foo"[low():])
}
panic: runtime error: slice bounds out of range

goroutine 1 [running]:
panic(0x102280, 0x1040a018)
/usr/local/go/src/runtime/panic.go:500 +0x720
main.main()
/tmp/sandbox685025974/main.go:12 +0x120

Full slice expressions

It applies only to array, pointer to array or slice (strings are excluded). It gives a control of the capacity in returned slice. With simple slice expression the capacity of returned slice is the maximum possible capacity starting at low i.e. cap(input) — low (playground):

numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s := numbers[1:4]
fmt.Println(s) // [1, 2, 3]
fmt.Println(cap(s)) // 9
For an array a cap(a) == len(a)

In above snippet the capacity of s is 9 since the slice starts at index 1 and the underlying array has 8 more elements (2–9). Full slice expressions allows amend this default behaviour (playground):

numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s := numbers[1:4:5]
fmt.Println(s) // [1, 2, 3]
fmt.Println(cap(s)) // 4

Full slice expression has the following syntax:

input[low:high:max]

Indices low and high work in the same way as with simple slice expressions. The only difference is max which sets result’s capacity to max — low (playground):

numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s := numbers[2:4:6]
fmt.Println(s) // [2, 3]
fmt.Println(cap(s)) // 4

While slicing an operand which is a slice, capacity is based on the operand, not the underlying array (playground):

numbers := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println(cap(numbers)) // 10
s1 := numbers[1:4]
fmt.Println(s1) // [1, 2, 3]
fmt.Println(cap(s1)) // 9
s2 := numbers[1:4:5]
fmt.Println(s2) // [1, 2, 3]
fmt.Println(cap(s2)) // 4
s3 := s2[:]
fmt.Println(s3) // [1, 2, 3]
fmt.Println(cap(s3)) // 4

Capacity of s3 cannot be extended to more than 4 (capacity of s2) even so the underlying has 10 elements and s1, s2 or s3 start at index 1.


Rule from the previous section (highlen(input)) has one exception. When the operand is a slice then high actually cannot be greater than cap(input) (playground):

numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s1 := numbers[0:1]
fmt.Println(s1) // [0]
fmt.Println(len(s1)) // 1
fmt.Println(cap(s1)) // 10
s2 := numbers[0:5]
fmt.Println(s2) // [0, 1, 2, 3, 4]
fmt.Println(cap(s1)) // 10

When it goes to max there are two additional rules regarding its value (playground):

  • high ≤ max
  • max ≤ cap(input)
numbers := [10]int{0,1,2,3,4,5,6,7,8,9}
s1 := numbers[0:1]
s2 := numbers[0:5:11] // invalid slice index 11 (out of bounds for 10-element array)
fmt.Println(s1, s2)

In full slice expression only low index is optional (playground):

numbers := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s := numbers[:4:6]
fmt.Println(s) // [0, 1, 2, 3]
fmt.Println(cap(s)) // 6

Omitting high isn’t allowed (playground):

numbers := [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
s1 := numbers[:4:6]
s2 := s1[::5]
fmt.Println(s2)
fmt.Println(cap(s2))

Such code fails to even compile — middle index required in 3-index slice.