Как влияет отсутствие указателей в методах Set и Get на поведение при записи и чтении в Go
Что проверяют
Понимаете ли вы разницу между получателем по значению и по указателю и знаете ли последствия, которые не видны в простом примере: наборы методов, интерфейсы, адресуемость. Следом часто спрашивают, почему вызов метода на элементе мапы не компилируется.
Как отвечать
Главное: в Go всё передаётся по значению, получатель метода тоже. Разница в том, что копируется.
func (c Config) Set(v int): метод получает копию структуры, запись уходит в копию и теряется. Компилятор промолчит,staticcheckв части случаев подсветит бесполезное присваивание;func (c Config) Get() int: для чтения это корректно, но при каждом вызове копируется вся структура. Для пары полей это дешевле указателя, для большой структуры заметно;- *`func (c Config) Set(v int)`**: копируется указатель, изменения видны вызывающему.
Дальше нюансы, ради которых вопрос и задают:
- наборы методов: у типа
Tтолько методы со значением, у*Tвсе. ЕслиSetобъявлен на указателе,Configне удовлетворяет интерфейсу сSet, а*Configудовлетворяет; - адресуемость:
cfg.Set(1)на переменной работает, Go сам возьмёт&cfg. На элементе мапыm["a"].Set(1)и на результате функции будет ошибка компиляции; - ссылочные поля: если в структуре слайс или мапа, запись в
c.items[0]через копию видна оригиналу, аappendнет. Поэтому код с получателем по значению иногда «наполовину работает»; sync.Mutexвнутри: копия структуры копирует и мьютекс, блокировка перестаёт защищать данные.go vetловит это проверкойcopylocks.
Правило из Go Code Review Comments: если хоть одному методу нужен указатель, делайте указателями все методы типа.
Где спотыкаются
Говорят, что указатель это «передача по ссылке». Указатель тоже копируется, просто копия указывает на те же данные.
Утверждают, что указатель всегда быстрее. Для маленьких структур копия дешевле, а указатель может увести значение в кучу и нагрузить сборщик мусора.
Смешивают получатели у одного типа, а потом не понимают, почему значение не подходит под интерфейс или почему изменение в цикле for _, v := range items пропало: v это копия элемента.