· 7 мин 👁 1.4k Начинающий

Язык Go — append

Встроенная функция append в Go — как работает, почему возвращает слайс, как добавлять элементы и склеивать слайсы через многоточие.

appendслайсыvariadicмноготочие
Содержание

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-слайсу работает корректно — удобно для накопления
  • Рост ёмкости происходит автоматически, точный коэффициент не гарантирован