Fixed-size arrays and the workhorse slice type.
Slices: make, append, copy
A slice is a flexible, dynamically-sized view into an array. It's the type you'll use for "a list of things" 99% of the time.
A slice is internally a tiny three-field struct: a pointer to the underlying array, a length, and a capacity.
Creating slices
nums := []int{1, 2, 3} // composite literal
empty := []int{} // empty (len=0, cap=0)
zeros := make([]int, 5) // [0 0 0 0 0]
buffer := make([]int, 0, 10) // len=0, cap=10 — room for 10 without realloc
Note the missing length in the type — []int is a slice; [3]int is an array.
append
append returns a new slice, possibly with a new backing array if there wasn't
enough capacity.
Slicing operator s[low:high]
s := []int{0, 1, 2, 3, 4}
mid := s[1:4] // [1 2 3] — high is exclusive
head := s[:2] // [0 1]
tail := s[3:] // [3 4]
all := s[:] // [0 1 2 3 4]
A slice of a slice shares the same underlying array. Mutating one can be visible to the other:
copy — make an independent slice
dst := make([]int, len(src))
n := copy(dst, src) // n == min(len(dst), len(src))
copy is how you produce an independent slice (no shared backing array).
Removing an element
There's no remove, but the idiom is short:
s = append(s[:i], s[i+1:]...) // preserves order
s[i] = s[len(s)-1]; s = s[:len(s)-1] // doesn't preserve order, but O(1)
Since Go 1.21, the slices package in the standard library gives you helpers
like slices.Delete, slices.Insert, slices.Contains, and slices.Sort:
import "slices"
s = slices.Delete(s, i, i+1)
Passing slices to functions
When you pass a slice to a function, you pass a copy of the slice header (pointer + len + cap) but you both see the same underlying array. So changes to elements are visible to the caller:
func zero(s []int) {
for i := range s { s[i] = 0 }
}
xs := []int{1, 2, 3}
zero(xs)
fmt.Println(xs) // [0 0 0]
But append inside a function may reallocate, in which case the caller's slice
is unchanged. Return the new slice from the function and reassign — same rule as
inside one function.