← Назад к вопросам
Что будет со слайсом, если взять его срез и сделать append?
2.0 Middle🔥 221 комментариев
#Основы Go#Производительность и оптимизация
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что будет со слайсом при append к его срезу
Это опасный трюк в Go, связанный с тем, что срезы (slices) — это представления (views) на массив, а не копии. Понимание этого поведения критично для избежания сложных багов.
Основной механизм
Срез — это структура из трёх компонентов:
slice := []int{1, 2, 3, 4, 5}
┌─────────────────────────────────┐
│ Underlying Array │
│ [1, 2, 3, 4, 5, ?, ?, ?...] │
└─────────────────────────────────┘
▲
pointer (указатель на первый элемент)
length: 5 (количество элементов)
capacity: 8 (всего памяти в массиве)
Сценарий 1: Append в срез с оставшейся ёмкостью
func main() {
original := []int{1, 2, 3, 4, 5} // capacity: 10 (обычно больше length)
// Берём срез с индекса 2
subslice := original[2:4] // [3, 4]
// subslice указывает на позицию 2 в исходном массиве
// length: 2, capacity: 8 (сколько осталось до конца underlying array)
fmt.Println("Before append:")
fmt.Println("original:", original) // [1 2 3 4 5]
fmt.Println("subslice:", subslice) // [3 4]
// Добавляем элемент в subslice
subslice = append(subslice, 99)
fmt.Println("\nAfter append:")
fmt.Println("original:", original) // [1 2 3 99 5] ⚠️ ИЗМЕНИЛОСЬ!
fmt.Println("subslice:", subslice) // [3 4 99]
// Исходный срез тоже изменился, потому что он указывает на тот же underlying array!
}
Почему это происходит:
subsliceуказывает на элемент с индексом 2 исходного массива- Append добавляет элемент в позицию 4 (следующую после конца subslice)
- Но это же положение (индекс 4) в исходном массиве!
- Поэтому
original[4]изменяется с 5 на 99
Сценарий 2: Append вызывает переаллокацию
func main() {
// Создаём массив с точной ёмкостью
original := make([]int, 5, 5) // length=5, capacity=5
copy(original, []int{1, 2, 3, 4, 5})
subslice := original[2:4] // [3, 4]
// capacity subslice = 3 (5 - 2, осталось до конца)
fmt.Println("Before append:")
fmt.Println("original:", original) // [1 2 3 4 5]
fmt.Println("subslice:", subslice) // [3 4]
fmt.Println("capacity:", cap(subslice)) // 3
// Добавляем МНОГО элементов, вызывающих переаллокацию
subslice = append(subslice, 10, 11, 12, 13)
fmt.Println("\nAfter append:")
fmt.Println("original:", original) // [1 2 3 4 5] - не изменилось!
fmt.Println("subslice:", subslice) // [3 4 10 11 12 13]
// На этот раз subslice указывает на НОВЫЙ underlying array!
// Потому что старого места не хватило
}
Почему так:
subsliceимеет capacity 3- Append пытается добавить 4 элемента
- Go выделяет новый больший массив и копирует данные
subsliceтеперь указывает на новый массивoriginalостаётся неизменным
Сценарий 3: Опасный баг - изменение через срезы
func main() {
original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Два среза от одного массива
slice1 := original[1:4] // [2, 3, 4]
slice2 := original[3:7] // [4, 5, 6, 7]
fmt.Println("Before:")
fmt.Println("original:", original)
fmt.Println("slice1:", slice1)
fmt.Println("slice2:", slice2)
// Modify через slice1
slice1[2] = 999 // Изменяем элемент с индексом 3 в original
fmt.Println("\nAfter slice1[2] = 999:")
fmt.Println("original:", original) // [1 2 3 999 5 6 7 8 9 10]
fmt.Println("slice1:", slice1) // [2 3 999]
fmt.Println("slice2:", slice2) // [999 5 6 7] ⚠️ Тоже изменился!
}
Как избежать этих проблем
1. Копируй данные, если нужно изменять
func safe() {
original := []int{1, 2, 3, 4, 5}
// Делаем копию
subslice := make([]int, len(original[2:4]))
copy(subslice, original[2:4])
// Теперь append не влияет на original
subslice = append(subslice, 99)
fmt.Println("original:", original) // [1 2 3 4 5] - не изменилось
fmt.Println("subslice:", subslice) // [3 4 99]
}
2. Используй full slice expression для управления ёмкостью
func controlCapacity() {
original := []int{1, 2, 3, 4, 5, 6, 7, 8}
// Берём срез БЕЗ резервной ёмкости
subslice := original[2:4:4] // [3, 4], capacity=0 (4-4)
// Теперь append вызовет переаллокацию сразу
subslice = append(subslice, 99)
fmt.Println("original:", original) // [1 2 3 4 5 6 7 8] - не изменилось
fmt.Println("subslice:", subslice) // [3 4 99]
}
3. Будь осторожен при возврате срезов из функций
// ❌ Опасно
func getBadSlice() []int {
arr := []int{1, 2, 3, 4, 5}
return arr[1:4] // Возвращаем срез локального массива!
}
// ✅ Лучше - копируй
func getGoodSlice() []int {
arr := []int{1, 2, 3, 4, 5}
result := make([]int, 3)
copy(result, arr[1:4])
return result
}
Практический пример: фильтрация с ошибкой
// ❌ Неправильно
func filterBad(data []int, predicate func(int) bool) []int {
result := make([]int, 0, len(data))
for _, v := range data {
if predicate(v) {
result = append(result, v)
}
}
return result // Может содержать нежелательные элементы
// если capacity был > len(data)
}
// ✅ Правильно
func filterGood(data []int, predicate func(int) bool) []int {
var result []int
for _, v := range data {
if predicate(v) {
result = append(result, v)
}
}
return result // Чистый срез без мусора в capacity
}
Ключевые выводы
- Срезы — это представления (views), а не копии данных
- Append в срез может изменить исходный массив, если есть свободная ёмкость
- Используй
copy()если нужны независимые данные - Full slice expression
s[i:j:k]контролирует ёмкость - Будь осторожен с возвратом срезов из функций
Это одна из самых хитрых особенностей Go, и понимание её избегает множество сложных багов в production коде.