Что нужно помнить при добавлении элементов в слайс Go без знания точного размера
Что проверяют
Понимаете ли вы, что слайс это заголовок с указателем на массив, длиной и ёмкостью, и что append может вернуть слайс уже на другом массиве. Отсюда растут и вопросы производительности, и самые неприятные ошибки с общей памятью.
Как отвечать
Главное правило: результат append всегда присваивают обратно. Функция может записать в существующий массив, а может выделить новый и скопировать данные, и тогда старая переменная смотрит на старое место.
s := make([]int, 0, 100) // длина 0, ёмкость 100
for i := 0; i < 100; i++ {
s = append(s, i) // без перевыделений
}Дальше по пунктам:
- есть верхняя оценка, задайте ёмкость через
make: это убирает серию перевыделений и копирований; - оценки нет, просто добавляйте. Рост амортизированный, а конкретная стратегия увеличения ёмкости это деталь реализации, опираться на её коэффициент нельзя;
- не путайте длину и ёмкость:
make([]int, 100)даёт сто нулей, иappendзапишет сто первым элементом; - передача в функцию: менять элементы внутри можно, но добавление увидит вызывающий, только если вернуть слайс.
Где спотыкаются
Забывают, что подслайсы делят один массив. Пока ёмкости хватает, append к укороченному слайсу перезаписывает соседние элементы исходного, а после перевыделения перестаёт: поведение зависит от истории, и баг получается плавающий. Спасает третий индекс в выражении среза или явное копирование.
Держат маленький кусок большого массива. Слайс из трёх элементов, вырезанный из миллиона, не даёт освободить весь массив под ним, поэтому долгоживущий результат копируют в новый слайс через copy.
Считают ёмкость формулой, опираясь на удвоение. Коэффициент роста менялся между версиями языка и отличается для разных размеров элемента, так что фактическую ёмкость спрашивают у cap, а не выводят на бумаге.