Имеет ли смысл передавать слайс по указателю в Go
Что проверяют
Понимаете ли вы, что такое слайс внутри: заголовок из указателя на массив, длины и ёмкости. Следом обычно дают функцию с append внутри и спрашивают, что увидит вызывающий код.
Как отвечать
Короткий ответ: обычно смысла нет. Слайс передаётся по значению, но копируется только заголовок, 24 байта на 64-битной платформе, независимо от числа элементов. Сам массив остаётся общим.
Отсюда поведение:
- изменение элементов:
s[0] = 100внутри функции видно снаружи и без указателя, массив ведь один; append: меняет длину в копии заголовка, поэтому вызывающий код новых элементов не увидит. Если ёмкости не хватило,appendвыделит новый массив, и дальше функция работает уже с ним;- идиома: вернуть слайс, как это делает сам
append:s = addItems(s, x).
Когда указатель на слайс оправдан:
- метод, который меняет длину: у именованного типа вроде
type Stack []intметодPushдолжен иметь получатель*Stack; - функция заполняет слайс вызывающего:
json.Unmarshal(data, &items)без указателя не смог бы вернуть новый заголовок; - редкие сигнатуры, где функция меняет слайс и одновременно возвращает другие значения, а добавлять слайс в результат неудобно.
Указатель на слайс не быстрее: добавляется лишнее разыменование, а переменная может уйти в кучу по результатам escape-анализа. Производительность здесь не аргумент.
Где спотыкаются
Говорят, что передавать большой слайс по значению дорого. Размер копии не зависит от длины.
Не знают про общий массив при append с запасом ёмкости. Функция дописала элемент в свою копию, и он перезаписал данные, которые у вызывающего лежали за пределами его длины, например в другом слайсе от того же массива. Защита: передавать s[:len(s):len(s)], чтобы append был вынужден выделить новый массив, или копировать через slices.Clone.
Приводят в пример указатель ради изменения элемента, хотя для этого он не нужен.