Что происходит при расширении слайса в Go
Что проверяют
Понимаете ли вы, что слайс это маленькая структура из указателя на массив, длины и ёмкости, и когда append пишет в старый массив, а когда переезжает в новый. Следом почти всегда дают задачку, где два слайса делят один массив, и просят предсказать вывод.
Как отвечать
Опишите механику по шагам:
- есть место: если
len + n <= cap,appendпишет элементы в тот же массив и возвращает заголовок с новой длиной. Выделения памяти нет; - места нет: рантайм вызывает
growslice, выделяет новый массив, копирует туда старые элементы и возвращает слайс, указывающий уже на него. Старый массив живёт, пока на него есть ссылки; - новая ёмкость: с Go 1.18 ёмкость удваивается, пока слайс меньше 256 элементов, дальше рост плавно снижается к множителю около 1.25. Если добавляют сразу больше, чем даст удвоение, берётся ровно нужный размер;
- округление: итог подгоняется под классы размеров аллокатора, поэтому фактическая
capбывает больше расчётной. Точные цифры это деталь реализации, опираться на них в коде нельзя.
Отсюда практические выводы. Если размер известен заранее, создавайте make([]T, 0, n) или вызывайте slices.Grow (Go 1.21+), чтобы не копировать массив на каждом росте. Результат append всегда присваивайте обратно: s = append(s, x). Функция, которая добавляет в слайс, должна вернуть его, иначе вызывающий код не увидит новую длину.
Хорошо добавить про полное выражение среза s[low:high:max]: оно ограничивает ёмкость, и следующий append гарантированно скопирует данные, не задев исходный массив.
Где спотыкаются
Говорят, что слайс «ссылочный тип» и изменения видны всем. Это верно только до переаллокации: после неё один слайс смотрит в новый массив, другой в старый, и они расходятся молча.
Называют рост «всегда в два раза». Формула менялась, до Go 1.18 порог был 1024 элемента, а округление под аллокатор делает результат неочевидным.
Берут маленький подслайс от огромного массива и держат его долго. Исходный массив не освобождается сборщиком мусора, пока жив подслайс, поэтому нужные данные лучше скопировать через copy или slices.Clone.