Какие проблемы могут возникнуть при преобразовании массива структур одного типа в другой с использованием указателей в Go
Что проверяют
Понимаете ли вы разницу между значением и указателем в Go, как устроен слайс и что происходит с переменной цикла. Следом часто спрашивают, можно ли привести []A к []B без копирования.
Как отвечать
Разберите, где здесь ломается:
- адрес переменной цикла: в
for _, p := range src { dst = append(dst, &p) }до Go 1.22 все указатели смотрели на одну переменную и после цикла вели к последнему элементу. С Go 1.22 (если вgo.modверсия не ниже 1.22) переменная своя на каждой итерации, но&pвсё равно адрес копии, а не элемента слайса. Нужен&src[i]; - устаревшие указатели:
&src[i]указывает в текущий базовый массив. Послеappend, который перевыделил память, указатель остаётся на старом массиве, и изменения расходятся; - удержание памяти: указатель на один элемент держит живым весь базовый массив, сборщик мусора его не освободит;
- приведение типов:
B(a)для структур разрешено, только если совпадают поля, их типы и порядок (теги с Go 1.8 не учитываются). А[]Aв[]Bтак не приводится, нужен цикл; unsafe:unsafe.Slice((*B)(unsafe.Pointer(unsafe.SliceData(a))), len(a))работает без копирования, но держится на совпадении раскладки в памяти. Кто-то добавит поле в одну структуру, и код продолжит компилироваться, читая мусор;- *
nilв `[]T**: слайс указателей может содержатьnil`, и обращение к полю упадёт паникой.
Стоит назвать и цену: []*T проигрывает []T по локальности кэша и добавляет работы сборщику мусора. Если функция просто перекладывает поля, честнее вернуть []B значениями.
И про массивы: [N]T в Go это значение, при передаче в функцию он копируется целиком, и изменения внутри функции до исходного массива не доходят.
Где спотыкаются
Думают, что копия указателя даёт независимую структуру. Наоборот: оба указателя ведут на один объект, изменение видно через оба.
Говорят «в Go 1.22 проблему с &v в цикле исправили» и не замечают, что указатель всё ещё на копию элемента.
Рассказывают про утечки «из-за незакрытых объектов». Память в Go освобождает сборщик, реальная проблема в удержании ссылок.