Как работает встроенная функция `append` для слайсов в Go?

Ответ

Функция append добавляет один или несколько элементов в конец слайса и возвращает новый слайс. Её работа зависит от ёмкости (capacity) исходного слайса.

Алгоритм работы:


  1. Проверка ёмкости: Go проверяет, достаточно ли места в нижележащем массиве (underlying array) для добавления новых элементов. Это сравнивается len(slice) + len(new_elements) с cap(slice).


  2. Если ёмкости достаточно (cap > len):

    • Новые элементы добавляются в существующий нижележащий массив сразу после последнего элемента.
    • Функция возвращает новый заголовок слайса, который указывает на тот же массив, но имеет обновленную длину (len).
  3. Если ёмкости недостаточно (cap == len):

    • Go выделяет новый, больший массив.
    • Стратегия роста: для небольших слайсов ёмкость обычно удваивается. Для слайсов размером более 1024 элементов рост замедляется до ~1.25x, чтобы избежать избыточного расхода памяти.
    • Все элементы из старого массива копируются в новый.
    • Новые элементы добавляются в конец нового массива.
    • Функция возвращает заголовок слайса, указывающий на этот новый массив.

Важный нюанс: append и нижележащий массив

Поскольку append может изменять существующий массив, это может приводить к неожиданному поведению, если несколько слайсов ссылаются на один и тот же массив.

package main

import "fmt"

func main() {
    // Создаем массив и два слайса, указывающих на него
    sourceArray := [5]int{10, 20, 30, 40, 50}
    slice1 := sourceArray[0:2] // {10, 20}, len=2, cap=5
    slice2 := sourceArray[1:3] // {20, 30}, len=2, cap=4

    fmt.Printf("До: slice1=%v, slice2=%v, array=%vn", slice1, slice2, sourceArray)

    // append для slice1 имеет достаточно ёмкости в sourceArray
    slice1 = append(slice1, 99)

    // Так как slice1 изменил нижележащий массив, slice2 тоже "увидел" это изменение
    fmt.Printf("После: slice1=%v, slice2=%v, array=%vn", slice1, slice2, sourceArray)
    // Вывод:
    // До: slice1=[10 20], slice2=[20 30], array=[10 20 30 40 50]
    // После: slice1=[10 20 99], slice2=[20 99], array=[10 20 99 40 50]
}

Золотое правило: Всегда присваивайте результат append обратно в переменную слайса, так как функция может вернуть как измененный старый, так и совершенно новый слайс.
slice = append(slice, element)