Что такое ресивер (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), если:

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

Ответ 18+ 🔞

А, ну так вот, ресивер, блядь! Это ж та самая штука, которая делает из обычной функции — метод, понимаешь? Такая приставка, которая говорит: «Эй, чувак, эта функция принадлежит вот этому типу, и вызывать её надо на нём, как myInstance.MyMethod()». Без этого — просто функция, скукота, а с ним — уже метод, всё по-взрослому.

Их, этих ресиверов, всего два вида, и тут важно не проебаться.

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

Это когда ты передаёшь не самого чувака, а его адресок, ссылочку, понимаешь?

  • Что делает? Даёт тебе полную власть, сука! Можешь менять оригинал сколько влезет. Всё, что внутри метода накосячишь, останется в исходном объекте навсегда. Как татуировка, блядь.
  • Зачем ещё? Экономия, ёпта! Если структура размером с хороший диван, копировать её каждый раз при вызове — это ж овердохуища ресурсов. А тут — чирик, передал адрес, и всё, ты уже внутри.

Смотри, как это выглядит:

type User struct { Name string }

// Метод с ресивером-указателем — тут власть!
func (u *User) SetName(name string) {
    u.Name = name // Меняем оригинал, и он меняется навсегда. Всё.
}

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

А это когда ты работаешь с ксерокопией, блядь. Призраком.

  • Что делает? Получает полную, но одноразовую копию объекта. Можешь внутри хоть перекрасить, переименовать — оригинал и ухом не поведёт. Изменения сгорят, как спичка.
  • Цена вопроса: Каждый вызов — новое копирование всей структуры. Для маленьких — похуй, а для монстров — пиздец как накладно.

И вот пример:

type User struct { Name string }

// Метод с ресивером-значением — работа с призраком
func (u User) GetName() string {
    // u.Name = "new name" // Хуй ты что изменишь, это же копия! Оригинал в безопасности.
    return u.Name
}

Так когда что брать-то, ёпта?

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

  • Тебе надо менять объект. Не просто посмотреть, а именно впендюрить новое значение.
  • Структура большая, и копировать её каждый раз — это, блядь, как таскать диван на пятый этаж без лифта. Нахуя?
  • Для консистентности, сука! Если хоть один метод у типа уже на указателе, то и остальные делай такими же. Чтобы не было этой хуйни: один метод меняет, а другой нет — волнение ебать, подозрение ебать чувствую!

Бери ресивер-значение (T), если:

  • Тип маленький и простой (как int, или маленькая структурка). Скопировать — раз плюнуть.
  • Тебе важна гарантия, что метод нихуя не тронет в оригинале. Чистое, непорочное чтение.
  • Тип по своей сути иммутабельный (как время, блядь, time.Time). Его и не должны менять.

Вот и вся философия. Главное — понимать, работаешь ты с оригиналом или с его ксерокопией. А то накосячишь — и потом сидишь, думаешь: «Почему у меня ничего не сохраняется, блядь?». А потому что ресивер не тот выбрал, вот почему!