Slices, Maps and Error Handling
Go's two everyday collection types, and a closer look at the error-handling pattern you've already seen in action.
Slices
A slice is a resizable, dynamically-sized view over an underlying array, Go's equivalent of a list:
scores := []int{90, 85, 77}
scores = append(scores, 100) // append returns a new slice, always reassign it
fmt.Println(scores[0]) // 90
fmt.Println(len(scores)) // 4
subset := scores[1:3] // elements at index 1 and 2
append might allocate a new underlying array behind the scenes if the old one ran out of room, that's exactly why you always write scores = append(scores, ...) instead of just calling it and discarding the result.
Maps
scoresByName := map[string]int{"Ada": 95, "Grace": 98}
scoresByName["Bob"] = 70
value, ok := scoresByName["Missing"]
fmt.Println(value, ok) // 0 false, ok tells you whether the key existed
delete(scoresByName, "Bob")
The comma-ok idiom (value, ok := ...) is how Go distinguishes "the key exists and its value is the zero value" from "the key doesn't exist at all", both would otherwise look identical.
Error handling recap
file, err := os.Open("data.txt")
if err != nil {
fmt.Println("Could not open file:", err)
return
}
defer file.Close() // runs when the surrounding function returns, guaranteed
defer schedules a call to run right before the enclosing function returns, no matter which return statement triggers it, exactly like a finally block, and the idiomatic way to guarantee cleanup (closing files, unlocking mutexes) happens.