18. Slices: the list you'll actually use
A slice is Go's growable list. It looks like an array with the size left out, and that one missing number changes everything: slices resize, they're cheap to pass around, and every Go program is full of them.
Declaring a slice
package main
import "fmt"
func main() {
names := []string{"Ada", "Grace", "Katherine"}
fmt.Println(names, len(names))
var empty []int
fmt.Println(empty, len(empty), empty == nil)
nums := []int{}
fmt.Println(nums, len(nums), nums == nil)
}
[]string — no number between the brackets. That's the entire syntactic
difference from an array, and it's how you tell them apart at a glance.
A var empty []int that's never assigned is nil. A nil slice is
perfectly usable: it has length 0, you can range over it, and you can
append to it. That's why var out []int is the idiomatic way to start
collecting results — you don't need to initialise anything.
Indexing and iterating
Exactly like arrays:
package main
import "fmt"
func main() {
langs := []string{"Go", "Rust", "Python"}
fmt.Println(langs[0], langs[len(langs)-1])
for i, lang := range langs {
fmt.Printf("%d: %s\n", i, lang)
}
langs[1] = "Zig"
fmt.Println(langs)
}
langs[len(langs)-1] is the idiom for "last element" — Go has no negative
indexing.
Growing with append
This is the function you'll call more than any other:
package main
import "fmt"
func main() {
var queue []string
queue = append(queue, "first")
queue = append(queue, "second")
queue = append(queue, "third", "fourth")
fmt.Println(queue, len(queue))
more := []string{"fifth", "sixth"}
queue = append(queue, more...)
fmt.Println(queue, len(queue))
}
Two things to burn in:
appendreturns a new slice header — you must assign the result. Writingappend(queue, "x")on its own line and expectingqueueto change is the single most common Go beginner bug. The compiler catches it (it complains the value is unused), but the habit matters.- To append one slice to another, spread it with
...— the same...you used for variadic functions, becauseappendis variadic.
Building a slice from a loop
The everyday pattern:
package main
import "fmt"
func main() {
var squares []int
for n := 1; n <= 6; n++ {
squares = append(squares, n*n)
}
fmt.Println(squares)
var long []string
for _, w := range []string{"go", "gopher", "concurrency", "map"} {
if len(w) > 3 {
long = append(long, w)
}
}
fmt.Println(long)
}
Start nil, append as you go. Go doesn't have list comprehensions; it has this, and every Go programmer reads it instantly.
make — pre-sizing a slice
When you know how many elements you're about to produce, tell Go up front:
package main
import "fmt"
func main() {
scores := make([]int, 3)
fmt.Println(scores, len(scores))
scores[0] = 90
fmt.Println(scores)
buf := make([]string, 0, 10)
fmt.Println(buf, len(buf), cap(buf))
}
make([]int, 3) gives you a slice of three zeroes — the elements exist, so
you assign by index rather than appending.
make([]string, 0, 10) is different: length 0 (nothing in it yet) but
capacity 10 — room for ten before Go has to allocate again. It's the
version you want when you'll append in a loop and know roughly how many.
That cap is the subject of the next lesson.
Watch the trap: make([]int, 3) followed by append gives you five
elements, not two — the first three are the zeroes you asked for.
Slices can't be compared with ==
package main
import "fmt"
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
// fmt.Println(a == b) // won't compile
fmt.Println(a == nil, b == nil)
equal := len(a) == len(b)
for i := range a {
if equal && a[i] != b[i] {
equal = false
}
}
fmt.Println("equal:", equal)
}
The only comparison a slice allows is against nil. For element-wise
equality you loop (or call slices.Equal from the standard library, which
you'll meet in the stdlib tour). Arrays compare, slices don't — that's the
price of being growable.
Your turn
Given a slice of temperatures, build a new slice containing only the ones
above freezing (> 0), then print it. The program should output exactly:
[12 5 30]
package main
import "fmt"
func main() {
temps := []int{12, -3, 5, -20, 30, 0}
// build `warm` with only the values greater than 0
fmt.Println(warm)
}
package main
import "fmt"
func main() {
temps := []int{12, -3, 5, -20, 30, 0}
var warm []int
for _, t := range temps {
if t > 0 {
warm = append(warm, t)
}
}
fmt.Println(warm)
}
Next: what append is really doing underneath, and why cap matters.