Что происходит со слайсом при вызове `append`?

Ответ

Слайс в Go — это структура-заголовок, содержащая три поля: указатель на базовый массив, длину (len) и ёмкость (capacity). Функция append добавляет элементы в конец слайса и возвращает обновленный слайс. Её поведение зависит от ёмкости исходного слайса.

Сценарий 1: Ёмкости достаточно (len < cap)

  1. Новый элемент добавляется в тот же базовый массив, на который указывает слайс.
  2. len слайса увеличивается, cap остается прежним.
  3. Указатель на массив не меняется. Новый слайс (возвращаемый append) и старый будут ссылаться на один и тот же базовый массив. Это может приводить к неожиданным побочным эффектам, если не быть осторожным.

Сценарий 2: Ёмкости недостаточно (len == cap)

  1. Выделяется память под новый, больший базовый массив. Его новая ёмкость обычно удваивается по сравнению со старой (для небольших слайсов).
  2. Все элементы из старого массива копируются в новый.
  3. Новый элемент добавляется в конец нового массива.
  4. append возвращает слайс, который указывает уже на этот новый массив. Старый массив, если на него больше нет ссылок, будет очищен сборщиком мусора.

Пример:

s1 := make([]int, 3, 4) // len=3, cap=4, массив: [0,0,0, _]
copy(s1, []int{1, 2, 3}) // s1 = [1, 2, 3]

// Сценарий 1: есть место в capacity
s2 := append(s1, 4) // len=4, cap=4. s2 использует тот же массив
fmt.Printf("s1: len=%d, cap=%d, %vn", len(s1), cap(s1), s1)
// s1: len=3, cap=4, [1 2 3]
fmt.Printf("s2: len=%d, cap=%d, %vn", len(s2), cap(s2), s2)
// s2: len=4, cap=4, [1 2 3 4]

// Сценарий 2: нет места, нужна новая аллокация
s3 := append(s2, 5) // len=5, cap=8 (удвоено). s3 указывает на новый массив
fmt.Printf("s3: len=%d, cap=%d, %vn", len(s3), cap(s3), s3)
// s3: len=5, cap=8, [1 2 3 4 5]

Ключевое правило

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

mySlice = append(mySlice, newElement)