Почему нельзя вернуть адрес локальной переменной из функции в Go?
Что проверяют
Не переносите ли вы правила C на Go. Вопрос сформулирован как утверждение, и проверяют, заметите ли вы подмену: в Go возвращать адрес локальной переменной можно, это законная и повседневная идиома.
Как отвечать
Скажите, что спецификация языка не говорит про стек и кучу вовсе, она гарантирует, что объект жив, пока на него есть ссылка. Где его разместить, решает компилятор:
func newCounter() *int {
n := 0
return &n // законно: n переедет в кучу
}Механизм называется анализом убегания. Если указатель покидает кадр функции, переменная размещается в куче, и сборщик освободит её, когда ссылок не останется. Решение компилятора видно при сборке с флагом -gcflags=-m. Именно на этом стоят конструкторы вида NewThing() *Thing и привычное return &Thing{}.
Дальше переведите разговор в цену. Корректность бесплатна, а вот аллокация нет: убегание переводит объект в кучу и добавляет работу сборщику. В горячем коде это видно в go test -bench с флагом -benchmem по числу выделений на операцию, и там обычно и находится оптимизация.
Полезно назвать, что действительно ломается: указатель на элемент слайса переживает append, и после перевыделения смотрит на старый массив, в старых версиях Go переменная цикла была одна на все итерации и адреса совпадали, а за границей unsafe и cgo гарантии рантайма заканчиваются.
Где спотыкаются
Отвечают заученным правилом из C и рассказывают про висячий указатель на освобождённый кадр. В Go без пакета unsafe висячих указателей не бывает, и одного этого ответа хватает, чтобы усомниться в опыте с языком.
Думают, что размещение выбирается ключевым словом: new в кучу, var на стек. Решает анализ убегания, а new и составной литерал в этом смысле равноправны.
Уходят в другую крайность: раз есть сборщик, аллокации бесплатны. Указатель на маленькую структуру ради экономии копирования часто дороже копии, потому что взятие адреса как раз и отправляет её в кучу.