В Go методы можно объявлять как для значения, так и для указателя на тип (value/pointer receiver). Эта особенность сохранилась с ранних версий языка для явного контроля, кто будет изменять исходные данные. Классическая проблема — необходимость расстояния между семантикой value (копия, не изменяет) и pointer (общий доступ к данным и возможность модификации).
Проблема — легко ошибиться, объявив метод с value receiver и не получив ожидаемого эффекта, либо вызвав value метод на pointer-переменной.
Решение — придерживаться следующих правил:
Пример кода:
type Counter struct { Value int } func (c Counter) IncCopy() { c.Value++ } // value receiver func (c *Counter) IncPointer() { c.Value++ } // pointer receiver c := Counter{} c.IncCopy() // Value останется 0 c.IncPointer() // Value станет 1
Ключевые особенности:
Можно ли вызвать value receiver-метод на указателе, и pointer-метод на значении?
Go "под капотом" автоматически разыменовывает указатели или берёт их адрес, поэтому вызов разрешён, если типы совместимы. Но не всегда — с интерфейсами это не работает так же предсказуемо.
var c Counter (&c).IncCopy() // Можно вызывать value-метод через указатель c.IncPointer() // Можно вызывать pointer-метод, Go автоматически возьмёт адрес
Что будет, если структура реализует только pointer-методы, но передать её по значению в интерфейс?
Такой объект не реализует интерфейс, если требует pointer-методы, поэтому возможен panic или компиляционная ошибка.
type D interface { IncPointer() } func f(d D) {} c := Counter{} f(c) // ошибка! Counter по значению не реализует интерфейс f(&c) // верно
Изменится ли структура при вызове метода pointer receiver, если передана копия указателя?
Да, даже если копируется указатель, под ним лежит один и тот же объект — результат будет одинаковым.
c := Counter{} p := &c p2 := p p2.IncPointer() // Value увеличится
Инженер реализует структуру с value receiver-методами "Update". Через интерфейс передаётся структура, но изменения "пропадают" — ведь работают с копией.
Плюсы:
Минусы:
Явное соглашение в команде: все методы с изменением состояния только pointer receiver, интерфейсы реализуются только указателями, value — для "расширений" и утилит.
Плюсы:
Минусы: