append and make
The literal []int{1, 2, 3} fits when the elements are known while writing the code. Even numbers from input or found words are collected one by one while the program runs. The window onto the lockers then has to grow.
The built-in append function adds a new element to the end of a slice:
package main
import "fmt"
func main() {
var s []int
fmt.Println(len(s), s == nil) // 0 true
s = append(s, 1)
s = append(s, 2, 3)
fmt.Println(s, len(s)) // [1 2 3] 3
}Let's walk through the example line by line:
var s []intdeclares a slice with no elements, so the firstPrintlnprints0 true.s = append(s, 1)returns the slice[1], and we assign it back intos.s = append(s, 2, 3)adds two values to the end at once.- The last
Printlnprints[1 2 3] 3.
The nil slice
The zero value of a slice is nil: a window that is not looking at any row yet. Such a slice has three properties:
- it has no elements, and
lenreturns0; fmt.Printlnprints it as[];appendworks with it, so a declarationvar s []intis enough for collecting values.
You can check whether the window is empty with s == nil. It is the only comparison Go allows for a slice.
Calling append
The call append(s, v) returns a slice in which v follows the last element of s. Let's look at its parts:
- the first argument is the slice being added to;
- the other arguments are the new elements, and one call can add several:
append(s, 2, 3); - the result is a slice, which is assigned back into the same variable.
A slice of the right length
When the length is known in advance, the slice is created with the built-in make function. It has two forms.
Length: make([]int, 3)
The call make([]int, 3) creates a slice of three zeros:
[]intis the slice type;3is the length, the number of elements.
The elements can be changed by index right away. These zeros are ordinary elements: append adds a new value after them, not in place of the first zero.
Length and capacity: make([]int, 0, 5)
A third argument sets the capacity—how many elements fit in the slice in total, counting the ones already created. In our picture it is the number of lockers from the window's left edge to the end of the row. The call make([]int, 0, 5) creates an empty slice with room for five elements, and cap(b) returns that number.
Both forms side by side:
a := make([]int, 3)
a[1] = 7
fmt.Println(a) // [0 7 0]
b := make([]int, 0, 5)
fmt.Println(len(b), cap(b)) // 0 5aholds three zeros, anda[1] = 7changes the middle one, so it prints[0 7 0].- In
bthe window shows no lockers at all, but the row has five: it prints0 5.
How a slice is built
A slice holds three values, and together they are called the slice header:
- a reference to the array where the elements live—this array is called the backing array;
- the length;
- the capacity—the number of elements of the backing array, counting from the start of the slice.
What append does depends on whether there is room left.
Capacity is enough
While the length is less than the capacity, append writes the new element into the same array, and the window widens by one locker. Nothing is copied.
Capacity runs out
When there is no room, append works in three steps:
- It creates a larger array.
- It copies the old elements into it.
- It returns a slice that points to the new array.
The new array is taken with spare room, so the following append calls again write without copying. How much larger it is is up to Go, and the exact factor depends on the version and on the slice's size.
Press the button and watch when append writes into the same array and when it moves the elements to a new one.
Slice: length 2, capacity 2 — no spare room. Press the button.
This is where the locker analogy ends: nobody moves real belongings to a new row by themselves. append does move them, and the old window keeps looking at the old row.
A slice from make([]int, 0, 5) has capacity 5: the first five append calls write into one array, and the sixth creates a new one. A variable that was not assigned the result still holds the old length.
Interviewers like to ask what a slice is and how append grows it. The answer has three parts:
- a slice is a header of a reference to a backing array, a length and a capacity;
- while there is free capacity,
appendwrites in place; when there is none, it creates a larger array and copies the elements into it; - the header after the call may be different, so the result is always assigned:
s = append(s, x).
Common mistake
The result of append was not assigned:
s := []int{1, 2, 3}
append(s, 4) // ✗append(s, 4) (value of type []int) is not usedThe compiler rejects such a call: append does not change the variable s, it only returns a new slice. Write s = append(s, 4).
Key points
appendreturns a new slice, and the result is assigned:s = append(s, v).make([]int, n)createsnzero elements, andmake([]int, 0, c)creates an empty slice of capacityc.- A slice is a header of a reference to a backing array, a length and a capacity.
- When capacity runs out,
appendcreates a larger array and copies the elements into it.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.