В чем разница между `pointer receiver` и `value receiver` в методах Go?

Ответ

Разница между pointer receiver (получатель-указатель) и value receiver (получатель-значение) заключается в том, получает ли метод указатель на исходный экземпляр или его полную копию.

Pointer Receiver (func (p *T) Method())

  1. Изменение состояния: Метод получает указатель на исходный объект. Это позволяет изменять состояние объекта внутри метода.
  2. Производительность: Эффективен для больших структур, так как в метод передается только указатель (обычно 4 или 8 байт), а не копируется вся структура.
  3. Работа с nil: Метод может быть безопасно вызван на nil-указателе. Внутри метода можно добавить проверку if p == nil.

Value Receiver (func (v T) Method())

  1. Неизменяемость (Immutability): Метод получает копию объекта. Все изменения внутри метода затрагивают только эту копию и не влияют на исходный объект. Это гарантирует, что метод не изменит вызывающий его экземпляр.
  2. Производительность: Может быть неэффективен для больших структур из-за накладных расходов на копирование.
  3. Работа с nil: Вызов метода на nil-указателе приведет к панике.

Пример:

package main

import "fmt"

type User struct {
    Name string
}

// Pointer receiver: может изменять исходный объект
func (u *User) SetName(newName string) {
    if u == nil {
        fmt.Println("Нельзя изменить nil-пользователя")
        return
    }
    u.Name = newName
}

// Value receiver: работает с копией
func (u User) Greet() {
    fmt.Printf("Привет, %s!n", u.Name)
}

func main() {
    user := User{Name: "Алекс"}
    user.Greet() // Привет, Алекс!

    user.SetName("Иван") // Изменяем исходный объект
    user.Greet() // Привет, Иван!

    var nilUser *User
    nilUser.SetName("Петр") // Безопасно, сработает проверка на nil
    // nilUser.Greet() // Приведет к панике
}

Рекомендации (Rule of Thumb):

  • Используйте pointer receiver, если метод должен изменять состояние объекта или если структура велика.
  • Используйте value receiver, если метод не изменяет состояние, а структура маленькая (например, слайс, time.Time) или вы хотите гарантировать неизменяемость.
  • Для консистентности, если хотя бы один метод типа имеет pointer receiver, остальные методы также должны иметь pointer receiver.