Ответ
Разрешение (поиск) методов в Go происходит во время компиляции и строго определяется понятием набора методов (Method Set). У каждого типа в Go есть свой набор методов.
Правила определения набора методов
Существует два простых правила:
- Набор методов типа
Tвключает все методы с ресивером (получателем) типаT. - Набор методов типа
*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): При встраивании одного типа в другой, методы встроенного типа добавляются в набор методов внешнего типа, что позволяет вызывать их напрямую.
Ответ 18+ 🔞
А, слушай, вот этот ваш Go, он же, блядь, как хитрая жопа — с виду просто, а под капотом такие правила, что мама не горюй. Речь про то, как он методы ищет, когда ты их вызываешь. Так вот, запомни раз и нахуй: всё решается на этапе компиляции, по чёткому понятию «набора методов» (Method Set). У каждого типа свой такой набор, как личное досье.
Два железобетонных правила, хуле
Заруби себе на носу, чтобы не выебываться потом:
- У типа
T(просто значение) в наборе — все методы, где ресивер (получатель) такой же —T. - А вот у типа
*T(указатель, звёздочка перед) — набор жирнее. Туда входят все методы с ресивером*T, плюс на халяву все методы с ресиверомT.
Короче, если перевести на русский: указатель — он умный, он «знает» про методы своего значения. А вот просто значение — тупое, про методы указателя него нихуя не знает.
Как это выглядит в жизни, когда код пишешь?
Компилятор Go — он не совсем уж мудак, иногда подкидывает синтаксический сахарок. Если видит, что ты пытаешься вызвать метод, а переменка не того вида, он может сам, по-тихому, взять адрес или разыменовать указатель. Смотри, не обосрись:
type User struct{
Name string
}
// Метод с ресивером-значением (в досье и у User, и у *User)
func (u User) GetName() string {
return u.Name
}
// Метод с ресивером-указателем (в досье ТОЛЬКО у *User)
func (u *User) SetName(name string) {
u.Name = name
}
func main() {
// Просто переменная, не указатель
u1 := User{Name: "Alice"}
u1.GetName() // ОК: GetName() есть в досье у User
u1.SetName("Bob") // И ЭТО ОК! Компилятор сам догадается сделать &u1
// А вот переменная-указатель
u2 := &User{Name: "Charlie"}
u2.GetName() // Тоже ОК! Компилятор разыменует: *u2
u2.SetName("David") // Ну тут всё ясно, SetName() в досье *User как родной
}
На что это всё влияет, кроме вызовов? А вот на что!
-
Интерфейсы, ёпта! Тип удовлетворяет интерфейсу только если его «досье» (набор методов) содержит все методы этого интерфейса. Вот почему методы, которые меняют состояние (типа
SetName), почти всегда делают с ресивером-указателем. Представь: если бы интерфейс требовалSetName, то его бы удовлетворял только*User, а простойUser— нихуя. Вот такой пиздец. -
Встраивание (Embedding) — это вообще отдельный цирк. Когда один тип встраиваешь в другой, методы встроенного типа автоматом попадают в «досье» внешнего. Можно вызывать их напрямую, будто они свои. Удобно, блядь, но если не понимать, откуда ноги растут, можно и ебаклак получить.