Что происходит с памятью при передаче параметров и возврате значений в функциях Go?
Что проверяют
Понимаете ли вы, что в Go всё передаётся по значению, и знаете ли, что размещение в куче решает не ваш выбор между значением и указателем, а анализ убегания на компиляции.
Как отвечать
Передачи по ссылке в языке нет. Копируется всегда, вопрос лишь в том, что именно:
- структура копируется целиком, массив тоже:
[1024]byteэто килобайт на каждый вызов; - указатель копируется как адрес, то есть одно машинное слово;
- слайс, строка, карта и канал копируются как небольшой заголовок, а нижележащие данные остаются общими;
- возврат значения тоже копия, но для небольших результатов она едет в регистрах и стоит почти ничего.
Где окажутся данные, решает компилятор. Если он доказал, что значение не переживёт кадр функции, оно останется на стеке, даже когда вы берёте у него адрес. Если адрес уезжает наружу, значение убегает в кучу, а это аллокация плюс работа сборщика. Возврат указателя на локальную переменную легален именно поэтому.
Проверять это нужно инструментами: go build -gcflags=-m показывает решения об убегании, go test -bench . -benchmem показывает аллокации на операцию. Разговор про дорого или дёшево без этих цифр беспредметен.
Где спотыкаются
Отвечают правилом «указатель дешевле копии». Для небольшой структуры чаще наоборот: значение убегает в кучу, добавляются разыменование и нагрузка на сборщик, тогда как копия в пару слов бесплатна. Правило звучит иначе: копируем, пока это не доказанное узкое место.
Забывают, что копия поверхностная. Скопировав структуру со слайсом или картой внутри, вы получили новый заголовок поверх тех же данных, и запись по индексу в копию видна в оригинале. Для настоящей копии нужен явный copy или обход.
Путают приёмники методов. Метод со значимым приёмником работает с копией, изменения полей наружу не попадают, а набор методов у типа и у указателя на него разный, поэтому смешанные приёмники ломают выполнение интерфейса в самый неудобный момент.