Что такое ресивер (receiver) в Go? В чем разница между ресивером-значением и ресивером-указателем?

Ответ

Ресивер (receiver) — это специальный параметр в объявлении метода, который связывает этот метод с определенным типом. Именно благодаря ресиверу мы можем вызывать метод на экземпляре типа, например myInstance.MyMethod().

Существует два типа ресиверов:

1. Ресивер-указатель (Pointer Receiver)

Использует указатель на тип (*T).

  • Эффект: Метод может изменять значение, на которое указывает ресивер. Все изменения будут видны в исходном объекте.
  • Производительность: Передается только указатель (адрес в памяти), что очень эффективно для больших структур, так как избегается полное копирование объекта.

Пример:

type User struct { Name string }

// Метод с ресивером-указателем
func (u *User) SetName(name string) {
    u.Name = name // Изменяет оригинальный объект User
}

2. Ресивер-значение (Value Receiver)

Использует сам тип (T).

  • Эффект: Метод работает с копией значения. Любые изменения внутри метода не затрагивают исходный экземпляр.
  • Производительность: Создается полная копия объекта при каждом вызове. Это может быть накладно для больших структур.

Пример:

type User struct { Name string }

// Метод с ресивером-значением
func (u User) GetName() string {
    // u.Name = "new name" // Это изменение затронет только копию
    return u.Name
}

Когда что использовать?

*Используйте ресивер-указатель (`T`), если:**

  • Метод должен изменять состояние объекта.
  • Структура большая, и вы хотите избежать накладных расходов на копирование при каждом вызове метода.
  • Для соблюдения консистентности: если хотя бы один метод типа имеет ресивер-указатель, остальные методы тоже должны его иметь, чтобы поведение было предсказуемым.

Используйте ресивер-значение (T), если:

  • Тип является небольшим и иммутабельным (например, базовые типы, маленькие структуры, слайсы).
  • Вам нужно гарантировать, что метод не изменит исходный объект.