Что такое набор методов (Method Set) в Go и как компилятор находит методы?

Ответ

Разрешение (поиск) методов в Go происходит во время компиляции и строго определяется понятием набора методов (Method Set). У каждого типа в Go есть свой набор методов.

Правила определения набора методов

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

  1. Набор методов типа T включает все методы с ресивером (получателем) типа T.
  2. Набор методов типа *T (указатель) включает все методы с ресивером *T, а также все методы с ресивером T.

Проще говоря, указатель «знает» о методах своего типа значения, но не наоборот.

Как это работает на практике?

Компилятор Go делает синтаксическое послабление: если необходимо, он может автоматически взять адрес переменной или разыменовать указатель для вызова метода.

type User struct{
    Name string
}

// Метод с ресивером-значением (входит в Method Set User и *User)
func (u User) GetName() string {
    return u.Name
}

// Метод с ресивером-указателем (входит только в Method Set *User)
func (u *User) SetName(name string) {
    u.Name = name
}

func main() {
    // Переменная-значение
    u1 := User{Name: "Alice"}
    u1.GetName() // OK: GetName() есть в наборе методов User
    u1.SetName("Bob") // OK: Компилятор неявно преобразует u1 в &u1

    // Переменная-указатель
    u2 := &User{Name: "Charlie"}
    u2.GetName() // OK: Компилятор неявно преобразует u2 в *u2 (разыменовывает)
    u2.SetName("David") // OK: SetName() есть в наборе методов *User
}

Важные аспекты


  • Удовлетворение интерфейсов: Тип удовлетворяет интерфейсу, только если его набор методов содержит все методы этого интерфейса. Это ключевая причина, почему методы, изменяющие состояние (как SetName), обычно объявляются с ресивером-указателем. Если бы SetName был нужен для удовлетворения интерфейса, то только *User удовлетворял бы ему, а User — нет.



  • Встраивание (Embedding): При встраивании одного типа в другой, методы встроенного типа добавляются в набор методов внешнего типа, что позволяет вызывать их напрямую.