Для какого механизма Go нужны методы с ресиверами по указателю
Что проверяют
Главный ответ здесь: интерфейсы и наборы методов. Интервьюер хочет услышать, почему значение типа иногда «не реализует» интерфейс, хотя метод у типа есть. Следом часто спрашивают про адресуемость и про nil-указатель внутри интерфейса.
Как отвечать
У каждого типа есть набор методов (method set), и по нему Go решает, реализует ли тип интерфейс:
- у
T: только методы с ресивером по значению; - *у `T`**: методы и с ресивером по значению, и с ресивером по указателю.
Если объявлен func (c *Counter) String() string, то var s fmt.Stringer = &c компилируется, а var s fmt.Stringer = c нет, с ошибкой «method String has pointer receiver». Интерфейс хранит копию значения, и адрес этой копии взять нельзя, а менять копию молча было бы ловушкой.
При прямом вызове асимметрии не видно: c.Inc() на адресуемой переменной компилятор сам превращает в (&c).Inc(). Но только на адресуемой: m["k"].Inc() для элемента мапы и вызов на результате функции не скомпилируются.
Остальные причины брать указатель:
- изменение состояния: ресивер по значению работает с копией, изменения теряются;
- запрет копирования: структура с
sync.Mutexвнутри,go vetругается на копирование замка; - размер: большую структуру дешевле не копировать, хотя маленькую копировать быстрее, а указатель может увести объект в кучу.
Правило из Go Code Review Comments: если хотя бы одному методу типа нужен указатель, делайте указателем все, чтобы набор методов был предсказуемым.
Где спотыкаются
Отвечают только «чтобы не копировать и менять поля». Это правда, но вопрос про механизм, и без наборов методов ответ неполный.
Говорят, что указатель всегда быстрее. Маленькую структуру дешевле скопировать, чем разыменовывать и нагружать сборщик мусора.
Возвращают типизированный nil, например переменную var e *MyErr, как error и проверяют err != nil. Интерфейс с типом и нулевым указателем внутри не равен nil, и проверка срабатывает там, где ошибки нет.