Ответ
Мутировать слайс в целом безопасно, но требует понимания его внутренней структуры, чтобы избежать неожиданного поведения. Слайс — это заголовок, содержащий три поля:
- Указатель на базовый массив (underlying array).
- Длина (
len
) — количество элементов в слайсе. - Ёмкость (
cap
) — количество элементов от начала слайса до конца базового массива.
Риски и поведение при мутации:
1. Изменение элементов существующего слайса
Если вы меняете элемент (slice[i] = newValue
), вы меняете значение в базовом массиве. Любой другой слайс, который ссылается на этот же массив, увидит эти изменения.
original := []int{1, 2, 3, 4}
sliceA := original[:2] // [1, 2]
sliceB := original[1:3] // [2, 3]
sliceA[1] = 99 // Меняем второй элемент
fmt.Println(original) // Вывод: [1, 99, 3, 4]
fmt.Println(sliceB) // Вывод: [99, 3] // sliceB тоже изменился!
2. Использование append
Поведение append
зависит от ёмкости (cap
) слайса.
-
append
без реаллокации (len < cap
): Новый элемент добавляется в базовый массив, используя доступную ёмкость. Это может "затереть" данные, видимые другим слайсам, которые ссылаются на ту же область памяти.original := []int{1, 2, 3, 4} // len=4, cap=4 sliceA := original[:2] // len=2, cap=4 sliceA = append(sliceA, 99) // Реаллокации нет, т.к. cap=4 fmt.Println(sliceA) // Вывод: [1, 2, 99] fmt.Println(original) // Вывод: [1, 2, 99, 4] // original был изменен!
-
append
с реаллокацией (len == cap
): Если ёмкости не хватает, Go выделяет новый, больший массив, копирует в него старые элементы, добавляет новый и возвращает слайс, указывающий уже на этот новый массив. Оригинальный массив и другие слайсы остаются нетронутыми.original := []int{1, 2} sliceA := original sliceA = append(sliceA, 3) // Произошла реаллокация sliceA[0] = 99 // Меняем элемент в НОВОМ массиве fmt.Println(sliceA) // Вывод: [99, 2, 3] fmt.Println(original) // Вывод: [1, 2] // original не изменился
Как безопасно мутировать:
- Для изоляции: Если нужно изменить слайс, не затрагивая оригинал, создайте полную копию с помощью функции
copy()
. - Контроль
append
: Всегда переприсваивайте результатappend
той же переменной (slice = append(slice, ...)
) , так как она может вернуть указатель на новый массив. - Конкурентность: При доступе к одному слайсу из нескольких горутин используйте мьютексы (
sync.Mutex
) для предотвращения гонок данных.