Зачем создавать слайс с длиной и емкостью вместо использования append в Go
Что проверяют
Понимаете ли вы, как слайс устроен внутри и что происходит при append, когда ёмкость кончилась. Следом обычно спрашивают про разницу между make([]T, n) и make([]T, 0, n) и про общий нижележащий массив.
Как отвечать
Слайс это заголовок из трёх полей: указатель на массив, длина и ёмкость. Пока len < cap, append пишет в следующий элемент. Когда места нет, рантайм выделяет массив побольше, копирует туда элементы, а старый массив достаётся сборщику мусора. Небольшие слайсы растут вдвое, после 256 элементов множитель плавно снижается примерно до 1,25, и итог округляется до классов размеров аллокатора.
Если итоговый размер известен, предвыделение убирает промежуточные аллокации и копирования:
make([]T, n): длина сразуn, заполняете по индексуs[i] = v. Подходит, когда каждый элемент точно будет записан;make([]T, 0, n): длина ноль, ёмкостьn, дальше обычныйappend. Удобно, когда элементы фильтруются и итоговое число не большеn;slices.Grow: добавляет ёмкость существующему слайсу, есть в стандартной библиотеке с Go 1.21.
Тот же принцип работает для make(map[K]V, n) и strings.Builder.Grow. Эффект проверяют бенчмарком с -benchmem по allocs/op, а линтер prealloc находит такие места сам.
Где спотыкаются
Пишут make([]int, n) и затем append в цикле. В начале слайса остаются n нулей, данные добавляются после них.
Забывают про общий массив. b := a[:2] и затем append(b, x) перезапишет a[2], если у a хватало ёмкости. Полное выражение a[:2:2] ограничивает ёмкость и заставляет append копировать.
Предвыделяют по максимуму «на всякий случай» или хранят маленький подслайс огромного буфера. Весь массив живёт, пока жив подслайс, поэтому нужную часть копируют через slices.Clone или copy.