Что такое указатели в Go
Что проверяют
Понимаете ли вы, что в Go всё передаётся по значению, а указатель лишь позволяет скопировать адрес вместо самих данных. Следом почти всегда спрашивают про ресиверы: когда писать func (s *Service), а когда func (s Service), и как это влияет на реализацию интерфейса.
Как отвечать
Начните с базы и сразу переходите к последствиям:
- синтаксис:
*Tэто тип «указатель на T»,&xберёт адрес,*pразыменовывает. Нулевое значение указателяnil, разыменованиеnilдаёт панику; - без арифметики: сдвинуть указатель, как в C, нельзя, такое возможно только через пакет
unsafe; - изменение аргумента: функция, получившая
*User, меняет исходную структуру, а получившаяUserработает с копией; - ресиверы: набор методов
Tвключает только методы с ресивером-значением, набор*Tвключает оба вида. Если методы объявлены на указателе, интерфейс реализует*T, аTнет; - escape analysis: компилятор сам решает, где жить переменной. Если адрес «убегает» из функции, значение уходит в кучу, увидеть это можно через
go build -gcflags=-m.
Стоит добавить, что слайсы, мапы и каналы уже содержат ссылку внутри, поэтому указатель на них нужен редко. Но слайс передаётся как заголовок по значению: append внутри функции может перевыделить массив, и вызывающий код этого не увидит.
Практическое правило: указатель берут, когда объект нужно менять, когда он большой или когда его нельзя копировать, как структуру с sync.Mutex внутри. Ресиверы в пределах одного типа держат одинаковыми.
Где спотыкаются
Считают, что указатель всегда быстрее. Маленькую структуру дешевле скопировать, а указатель может вытолкнуть значение в кучу и добавить работы сборщику мусора.
Путают наборы методов: объявляют метод на указателе, кладут в интерфейс значение и получают ошибку компиляции «does not implement».
Рассказывают про ловушку с &v в цикле for range как про актуальную. Для модулей с go 1.22 и выше в go.mod у каждой итерации своя переменная, так что ошибка встречается уже только в старом коде.