Язык Go — append
Встроенная функция append в Go — как работает, почему возвращает слайс, как добавлять элементы и склеивать слайсы через многоточие.
Содержание
append — встроенная функция для добавления элементов в слайс. Выглядит как обычная функция, но реализована компилятором: в Go нельзя написать функцию, тип параметров которой определяется вызывающим кодом.
Сигнатура
Схематически append выглядит так:
func append(slice []T, elements ...T) []T
T — заглушка для любого типа. При вызове компилятор подставляет конкретный тип сам. Именно поэтому append встроена: обычная функция так не умеет.
Добавление элементов
append добавляет элементы в конец слайса и возвращает результат. Результат нужно сохранять — при нехватке ёмкости Go выделяет новый массив, и старая переменная будет указывать не туда:
x := []int{1, 2, 3}
x = append(x, 4, 5, 6) // добавляем сразу несколько элементов
fmt.Println(x) // [1 2 3 4 5 6]
Если забыть присвоить результат обратно — изменения потеряются:
x := []int{1, 2, 3}
append(x, 4) // результат выброшен — x не изменился
fmt.Println(x) // [1 2 3]
Как растёт слайс под капотом
Слайс — это тройка: указатель на массив, длина, ёмкость. Когда длина достигает ёмкости, append выделяет новый массив большего размера и копирует данные:
s := make([]int, 0, 3) // длина 0, ёмкость 3
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) // len=0 cap=3 []
s = append(s, 1)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) // len=1 cap=3 [1]
s = append(s, 2, 3)
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) // len=3 cap=3 [1 2 3]
s = append(s, 4) // ёмкость исчерпана — Go выделяет новый массив
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) // len=4 cap=6 [1 2 3 4]
// ёмкость выросла (обычно вдвое для небольших слайсов)
Точный коэффициент роста не гарантирован спецификацией — он зависит от версии Go и размера элементов.
Склейка двух слайсов через …
Чтобы добавить все элементы одного слайса в другой — используем ... при вызове. Без него код не скомпилируется: y имеет тип []int, а append ожидает int:
x := []int{1, 2, 3}
y := []int{4, 5, 6}
x = append(x, y...) // ... разворачивает y в список аргументов
fmt.Println(x) // [1 2 3 4 5 6]
// x = append(x, y) // ошибка компиляции: cannot use y (type []int) as type int
Тот же приём работает для копирования слайса с добавлением элементов:
a := []string{"go", "is"}
b := []string{"fast", "and", "fun"}
result := append([]string{}, a...) // копируем a в новый слайс
result = append(result, b...) // добавляем b
fmt.Println(result) // [go is fast and fun]
// a и b не затронуты
append к nil-слайсу
nil-слайс — валидный аргумент для append. Это удобно для постепенного накопления значений без предварительной инициализации:
var lines []string // nil, но append работает
for i := 0; i < 3; i++ {
lines = append(lines, fmt.Sprintf("строка %d", i))
}
fmt.Println(lines) // [строка 0 строка 1 строка 2]
Фильтрация на месте
Типичный паттерн — фильтрация слайса без выделения нового массива. Используем исходный слайс как накопитель:
nums := []int{1, -2, 3, -4, 5, -6}
positive := nums[:0] // пустой слайс, но тот же массив
for _, n := range nums {
if n > 0 {
positive = append(positive, n) // пишем поверх исходного массива
}
}
fmt.Println(positive) // [1 3 5]
Работает потому что количество результатов никогда не превышает количество входных элементов — запись не обгоняет чтение.
Итоги
appendвстроена в язык — обычная функция с обобщённым типом в Go так не работает- Всегда сохраняйте возвращённый слайс:
x = append(x, ...)— при росте создаётся новый массив - Несколько элементов за раз:
append(s, a, b, c) - Склейка двух слайсов:
append(a, b...)— без...не скомпилируется appendкnil-слайсу работает корректно — удобно для накопления- Рост ёмкости происходит автоматически, точный коэффициент не гарантирован