Following the Learn Go with Tests guide, exploring Go’s collection types: arrays and slices.
Arrays vs Slices #
Arrays: Fixed size, encoded in the type
numbers := [5]int{1, 2, 3, 4, 5} // fixed size array
numbers := [...]int{1, 2, 3, 4, 5} // compiler counts elementsSlices: Dynamic size, more flexible
numbers := []int{1, 2, 3, 4, 5} // slice (no size specified)[5]int and [4]int are different types - you can’t pass a 4-element array to a function expecting a 5-element array.
Building a Sum Function #
Starting with arrays, then refactoring to slices:
func Sum(numbers []int) int {
sum := 0
for _, number := range numbers {
sum += number
}
return sum
}Range Keyword:
- With index:
for i, number := range numbers - Index ignored:
for _, number := range numbers
Comparing Slices #
Go doesn’t allow == comparison for slices. Use slices.Equal():
import "slices"
func TestSum(t *testing.T) {
got := []int{3, 9}
want := []int{3, 9}
if !slices.Equal(got, want) {
t.Errorf("got %v want %v", got, want)
}
}Creating Slices #
Using make() #
// Create slice with specific length and capacity
sums := make([]int, 5) // length 5, capacity 5
sums := make([]int, 0, 10) // length 0, capacity 10Using append() #
var sums []int // zero-value slice (nil)
sums = append(sums, 42) // append returns new sliceImportant: append() returns a new slice - always assign the result back.
Variadic Functions #
Accept variable number of arguments:
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Sum(numbers))
}
return sums
}
// Usage
result := SumAll([]int{1, 2}, []int{3, 4}, []int{5})Slice Operations #
Slicing Slices #
numbers := []int{1, 2, 3, 4, 5}
tail := numbers[1:] // [2, 3, 4, 5] - from index 1 to end
head := numbers[:2] // [1, 2] - from start to index 2
middle := numbers[1:4] // [2, 3, 4] - from index 1 to 4Safe Slicing #
Always check bounds to avoid runtime panics - use len() to verify slice length before slicing.
Test Helper Functions #
Extract common test logic for cleaner tests:
func TestSumAllTails(t *testing.T) {
checkSums := func(t testing.TB, got, want []int) {
t.Helper()
if !slices.Equal(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
t.Run("normal slices", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
}Test Coverage #
Go includes built-in coverage analysis:
go test -coverExample output:
PASS
coverage: 100.0% of statementsMemory Management Examples #
Slice References vs Copies #
x := [3]string{"Лайка", "Белка", "Стрелка"}
y := x[:] // slice points to array x
z := make([]string, len(x))
copy(z, x[:]) // z is independent copy
y[1] = "Belka" // changes both x and y
// x: [3]string [Лайка Belka Стрелка]
// y: []string [Лайка Belka Стрелка]
// z: []string [Лайка Белка Стрелка] (unchanged)Key Point: Slices share underlying arrays unless explicitly copied.
Memory Leak Prevention #
a := make([]int, 1e6) // 1 million element slice
b := a[:2] // small slice, but keeps entire array in memory
c := make([]int, len(b)) // create independent copy
copy(c, b) // now 'a' can be garbage collectedLearn More: Go Slices Blog