Странный синтаксис Go: что означают скобки перед именем функции?
Это ресивер (получатель) — основа методов в Go
То, что вы видите в скобках перед именем функции, называется ресивером (receiver). В Go нет классов и ключевого слова self или this. Вместо этого вы привязываете функцию к конкретному типу через ресивер. Именно это превращает обычную функцию в метод этого типа.
Ваш пример с Rectangle:
type Rectangle struct {
Width float64
Height float64
}
// Метод с ресивером Rectangle
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
Здесь (r Rectangle) означает: "функция Area принадлежит типу Rectangle". Теперь вы можете вызывать её через точку:
rect := Rectangle{Width: 5, Height: 10}
area := rect.Area() // 50
Value receiver vs Pointer receiver
Вы заметили два варианта: (r Rectangle) и (p *Person). Это ключевое различие:
- Value receiver (по значению) —
(r Rectangle)— метод получает копию структуры. Изменения внутри метода не влияют на оригинал. Безопасно для чтения. - Pointer receiver (по указателю) —
(p *Person)— метод получает указатель на оригинал. Изменения сохраняются. Эффективно для больших структур.
Пример с указателем:
type Person struct {
Name string
}
// Pointer receiver — можем менять оригинал
func (p *Person) SetName(name string) {
p.Name = name
}
// Value receiver — меняем только копию
func (p Person) SetNameFail(name string) {
p.Name = name
}
func main() {
person := Person{Name: "Alice"}
person.SetName("Bob") // Работает: person.Name = "Bob"
person.SetNameFail("Carol") // Не работает: person.Name остаётся "Bob"
}
Когда что использовать?
- Pointer receiver нужен, если метод изменяет состояние структуры или структура большая (экономия памяти).
- Value receiver используйте для маленьких, неизменяемых структур или когда нужна гарантия, что оригинал не изменится.
- Если у вас есть хотя бы один метод с pointer receiver, всегда используйте pointer receiver для всех методов этого типа — это соглашение в Go.
А что если убрать ресивер?
Тогда это будет обычная функция, не привязанная к типу. Она работает, но теряет связь с данными:
func Area(r Rectangle) float64 {
return r.Width * r.Height
}
// Вызов: Area(rect) вместо rect.Area()
Это разный стиль. Методы (с ресивером) — для объектно-ориентированного подхода. Функции — для утилит. Go не запрещает ни то, ни другое, но идиоматический Go использует ресиверы для логики, работающей с данными конкретного типа.
Надеюсь, теперь стало понятнее! Ресивер — это как self в Python, но явно объявленный и с контролем по значению/указателю.
Дополнение: Ресиверы и интерфейсы
Важно понимать, что выбор между (t T) и (t *T) влияет на то, какие интерфейсы реализует ваш тип. В Go интерфейс считается реализованным, если у типа есть все методы с соответствующими ресиверами.
Пример:
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
// Value receiver
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func main() {
var s Shape
c := Circle{Radius: 5}
s = c // Работает: Circle (value) реализует Shape
s = &c // Тоже работает: *Circle тоже реализует Shape
// А если бы Area была на *Circle, то s = c не сработало бы
}
Правило: если метод объявлен на *T, то только указатель *T удовлетворяет интерфейсу. Если на T — то и T, и *T работают. Это частый источник ошибок у новичков, когда интерфейс неожиданно не реализуется.
Совет: для структур, которые будут передаваться через интерфейсы, почти всегда используйте pointer receiver (*T), чтобы избежать путаницы.
Ответов пока нет.
Войдите чтобы оставить ответ