История вопроса:
Срезы (slices) — одна из ключевых динамических структур в Go, появившаяся как альтернатива массивам фиксированной длины для повышения удобства и рентабельности памяти. Они обеспечивают гибкую работу с подмножествами массивов, но имеют ряд тонкостей, важных для производительного и безопасного кода.
Проблема:
Многие разработчики не понимают, как именно устроены срезы: slice — это не сам массив, а структура с указателем на массив, длиной и вместимостью (capacity). Это может приводить к утечкам памяти, багам при работе с копиями и неожиданным эффектам при изменении исходного массива.
Решение:
Slice — это тип:
type slice struct { ptr unsafe.Pointer len int cap int }
При расширении slice с помощью append() может произойти перераспределение backing array, и все прежние ссылки на старый массив останутся валидными, но будут ссылаться на старые данные. Незнание этой особенности приводит к ошибкам и memory leak.
Пример корректного выделения памяти и копирования:
src := []int{1,2,3,4,5} dst := make([]int, len(src)) copy(dst, src)
Slice, созданный с помощью [:], разделяет underlying array, и их модификация воздействует друг на друга, если не выполнено copy.
Ключевые особенности:
Что произойдет при увеличении slice через append превышая cap, если у других slice есть ссылки на тот же массив?
append при превышении cap создаёт underlying array с новым размещением в памяти, и только этот slice ссылается на новый массив, тогда как остальные — на старый. Это частая причина расхождения данных.
Почему важно не хранить длинно живущие slice небольшого размера, полученные из большого массива?
Даже если slice очень мал, его указатель хранит ссылку на весь backing array, что может привести к удержанию большого массива в памяти и memory leak.
Что будет, если слайсировать массив за его пределами?
Возникнет panic: runtime error: slice bounds out of range.
Функция читает большой файл в массив байт и возвращает slice первых 100 элементов. Этот slice потом хранится долго, но вся память под большой массив остается в GC.
Плюсы:
Минусы:
Сразу после получения slice производится копирование нужного куска в новый slice с make и copy. Старый массив сразу забывается, GC освобождает память.
Плюсы:
Минусы: