In Go we use slices to represent parts of an underlying array. Slices, unlike arrays, can be changed easily—they are views into the underlying data.
In Go slices have underlying arrays. A slice has no size as part of its type. And with built-in methods like cap()
and append()
we test and mutate slices.
Append
This example creates a slice of strings. It initially has 3 elements. We then use the append built-in to add 2 more strings to the slice.
package main
import "fmt"
func main() {
elements := []string{"cat", "dog", "bird"}
elements = append(elements, "fish", "snake")
fmt.Println(elements)
}[cat dog bird fish snake]
A slice has an underlying array. This array has a capacity (a size). This is the number of elements that the slice can hold before being resized.
cap()
built-in tells us the internal allocation heuristics of a slice. When it runs out of room, a slice's array doubles in size.package main
import "fmt"
func main() {
elements := []int{100, 200, 300}
// Capacity is now 3.
fmt.Println(cap(elements))
// Append another element to the slice.
elements = append(elements, 400)
// Capacity is now 6.
// ... It has doubled.
fmt.Println(cap(elements))
}3
6
The len
built-in returns the element count of a slice. An empty slice has a length of 0. This is not the same as capacity—only existing elements are counted.
package main
import "fmt"
func main() {
// Create an empty slice.
// ... Its length is 0.
items := []string{}
fmt.Println(len(items))
// Append a string and the slice now has a length of 1.
items = append(items, "cat")
fmt.Println(len(items))
}0
1
With the range keyword we can loop over a slice. The value returned on each iteration is the index into the slice. We can access an element with it.
package main import "fmt" func main() { animals := []string{"bird", "dog", "fish"} // Loop over the slice. for v := range animals { fmt.Println(animals[v]) } }bird dog fish
A three-part for
-loop can be used to iterate over a slice. We start at 0 and continue while the index is less than the length of the slice (found with len
).
package main import "fmt" func main() { colors := []string{"blue", "yellow", "orange"} // Loop over all indexes with a three-part for-loop. for v := 0; v < len(colors); v++ { fmt.Println(colors[v]) } }blue yellow orange
With this method we can create slices of a type and size. Internally slices must be initialized, and the make()
method does this for us.
make()
is the type of the slice. The second argument is the length of elements. The third is the capacity.package main import "fmt" func main() { // Create a slice of 5 integers. values := make([]int, 5) // Assign some elements. values[0] = 100 values[4] = 200 // Loop over elements in slice and display them. for v := range values { fmt.Println(values[v]) } }100 0 0 0 200
This built-in method receives two arguments: the destination slice and the source slice. The elements from the second arguments are copied into the first argument.
package main
import "fmt"
func main() {
slice1 := []int{10, 20, 30}
slice2 := []int{0, 0, 0, 1000}
// Display slice1 and slice2.
fmt.Println(slice1)
fmt.Println(slice2)
// Copy elements from slice1 into slice2.
copy(slice2, slice1)
// Slice2 now has values from slice1.
fmt.Println(slice2)
}[10 20 30]
[0 0 0 1000]
[10 20 30 1000]
This program creates a byte slice. It uses a string
to initialize the slice. It then displays the byte
count (with len
) and converts the bytes back into a string
.
package main
import "fmt"
func main() {
values := []byte("birds")
fmt.Println(values)
// Display length of byte slice.
fmt.Println(len(values))
// Create string copy of byte slice.
copy := string(values)
fmt.Println(copy)
}[98 105 114 100 115]
5
birds
A slice is versatile. Once we have a slice, we can take further slices of it (subslices). We use the slice syntax on a slice of any element type.
int
slice. We take a subslice of the middle, end and start elements. This returns new partial slices.package main
import "fmt"
func main() {
value := []int{10, 20, 30, 40, 50}
// Get slice of slice from index 2 to 4.
// ... Last index is exclusive.
partial := value[2:4]
fmt.Println(partial)
// Get slice from index 3 to end.
partial = value[3:]
fmt.Println(partial)
// Get slice from start to index 3.
partial = value[:3]
fmt.Println(partial)
}[30 40]
[40 50]
[10 20 30]
Remove
elementSlices do not have good support for element removal. Elements after the removed one must be shifted forward, which is slow.
string
"snake" is eliminated from the result slice.package main
import "fmt"
func main() {
// Example slice.
animals := []string{"cat", "gopher", "snake", "bird", "dog"}
// We want to remove element 2, which is snake.
removeIndex := 2
// Create a new empty slice.
result := []string{}
// Append part before the removed element.
// ... Three periods (ellipsis) are needed.
result = append(result, animals[0:removeIndex]...)
// Append part after the removed element.
result = append(result, animals[removeIndex+1:]...)
// Display results.
fmt.Println(result)
}[cat gopher bird dog]
Index
out of rangeA slice has a size. And if we access an element out of the range of the size, we will get a runtime error. We must first check the len
of a slice.
package main
func main() {
items := []int{10, 20, 30}
// This will cause an error.
items[100] = 10
}panic: runtime error: index out of range
Often Go slice initializers use constants like 10 and 20. But we can also use variables (of the correct type).
package main import "fmt" func main() { for i := 0; i < 3; i++ { items := []int{i, i + 1} fmt.Println(items) } }[0 1] [1 2] [2 3]
A string
contains runes. We can get a slice of runes from a string
with a conversion. We can modify the rune
slice.
rune
slice back into a string
with a built-in method. A rune
is a character in the string.package main
import "fmt"
func main() {
// Create a rune slice from a string.
animal := "cat"
slice := []rune(animal)
// Modify the rune slice.
slice[0] = 'm';
fmt.Println(slice)
// Convert the rune slice back into a string.
original := string(slice)
fmt.Println(original)
}[109 97 116]
mat
String
slice mapSlices are separate from maps. But we can put slices (like string
slices) in the values of a map and then append to those slices.
string
With strings.Join
we can convert a slice into a string
. For string
slices, this is easy. But for int
slices, we must have some special logic.
The term "idiomatic" refers to languages. An idiomatic usage is one that sounds natural and is easy to understand. Idiomatic Go uses slices. We emphasize them over arrays.