Для чего используется ресивер-указатель (pointer receiver) в методах Go?

Ответ

Ресивер-указатель (func (p *Type) Method()) используется в Go по двум основным причинам:


  1. Для изменения состояния объекта. Метод с ресивером-указателем получает указатель на исходный экземпляр, что позволяет изменять его поля. Методы с ресивером-значением (func (v Type) Method()) работают с копией объекта, и все изменения внутри метода не затрагивают оригинал.



  2. Для повышения производительности. При передаче больших структур в качестве ресивера-значения создается полная копия этой структуры. Использование указателя позволяет избежать этого копирования, передавая только адрес в памяти, что более эффективно.


Пример:

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

// Метод с ресивером-указателем (изменяет оригинал)
func (u *User) SetAge(newAge int) {
    u.Age = newAge
}

// Метод с ресивером-значением (работает с копией)
func (u User) Greet() {
    fmt.Printf("Привет, меня зовут %s, мне %d лет.n", u.Name, u.Age)
}

func main() {
    user := User{Name: "Алекс", Age: 30}
    user.Greet() // Привет, меня зовут Алекс, мне 30 лет.

    user.SetAge(31)
    user.Greet() // Привет, меня зовут Алекс, мне 31 год.
}

Важные правила и соглашения:

  • Согласованность: Если хотя бы один метод типа имеет ресивер-указатель, то все методы этого типа должны иметь ресивер-указатель. Это делает API типа предсказуемым.
  • Автоматическое разыменование/взятие адреса: Go автоматически преобразует значение в указатель (и наоборот), когда это необходимо. Можно вызывать user.SetAge(31) вместо (&user).SetAge(31).
  • Интерфейсы: Если тип T реализует интерфейс с помощью ресивера-значения, то и T, и *T удовлетворяют этому интерфейсу. Если же реализация идет через ресивер-указатель, то интерфейсу удовлетворяет только *T.