В каких случаях `append` модифицирует исходный срез, а в каких создает новый?

Ответ

Встроенная функция append всегда возвращает новый срез, но он может указывать как на тот же базовый массив (underlying array), что и исходный срез, так и на совершенно новый.

Поведение зависит от capacity (емкости) исходного среза:

  1. Если capacity достаточна: Новый элемент добавляется в существующий базовый массив. Возвращается новый срез, который указывает на тот же массив, но имеет увеличенную длину (len).

    • Важный побочный эффект: Так как базовый массив общий, его изменение будет видно через оба среза (старый и новый), что может привести к неожиданному поведению.

  2. Если capacity недостаточна: Go выделяет новый, больший массив, копирует в него все элементы из старого, добавляет новый элемент и возвращает срез, указывающий на этот новый массив.


Пример:

// Сценарий 1: Емкости хватает
original := make([]int, 2, 5) // len=2, cap=5
original[0], original[1] = 10, 20
fmt.Printf("Original: len=%d, cap=%d, %vn", len(original), cap(original), original)

appended := append(original, 30)
fmt.Printf("Appended: len=%d, cap=%d, %vn", len(appended), cap(appended), appended)

// Изменим элемент в новом срезе
appended[0] = 99
// Изменение отразится и в исходном, т.к. массив один и тот же!
fmt.Printf("Original after change: %vn", original) // Вывод: [99 20]

// Сценарий 2: Емкости не хватает, создается новый массив
slice := []int{1, 2, 3} // len=3, cap=3
newSlice := append(slice, 4)

newSlice[0] = 100
fmt.Println("slice:", slice)          // Вывод: [1 2 3] (не изменился)
fmt.Println("newSlice:", newSlice)  // Вывод: [100 2 3 4]

Ключевой вывод и лучшая практика: Всегда присваивайте результат append переменной, чаще всего той же самой: mySlice = append(mySlice, newValue). Это гарантирует, что вы работаете с корректным (возможно, новым) срезом.