Методы в Go: короткая инструкция для тех, кто начинает
Go обходится без классов, но методы у типов есть — и работают они не так, как в Python или Java. Разбираемся, как объявить метод, чем отличается приёмник-значение от приёмника-указателя и где новичок ломается чаще всего.

Что вы получите и что нужно до начала
К концу этого материала вы будете уверенно объявлять методы на своих типах, понимать разницу между value receiver и pointer receiver и перестанете удивляться, почему изменения внутри метода иногда «не доходят» до вызывающего кода. На всё — минут двадцать практики.
Что должно быть готово
- Go 1.21+ установлен и доступен в терминале (
go versionвыводит версию). - Любой текстовый редактор или IDE с поддержкой Go (VS Code с расширением Go, GoLand, даже
nanoподойдёт). - Базовое понимание структур (
struct) — знаете, как объявить и создать экземпляр.
Шаг 1. Объявляем первый метод
Метод в Go — обычная функция, у которой перед именем стоит приёмник (receiver). Приёмник привязывает функцию к конкретному типу. Вот минимальный пример целиком:
package main
import "fmt"
// Wallet хранит баланс в копейках.
type Wallet struct {
BalanceKopecks int
}
// DisplayBalance выводит баланс в рублях.
func (w Wallet) DisplayBalance() {
fmt.Printf("Баланс: %d.%02d руб.\n", w.BalanceKopecks/100, w.BalanceKopecks%100)
}
func main() {
my := Wallet{BalanceKopecks: 150_00}
my.DisplayBalance()
}
Запустите:
go run main.go
Проверка: в терминале появится Баланс: 150.00 руб.. Если видите ошибку компиляции — убедитесь, что файл сохранён с расширением .go и начинается с package main.
Часть (w Wallet) перед именем функции — и есть приёмник. w внутри метода работает как обычная переменная типа Wallet.
Шаг 2. Value receiver vs pointer receiver
Это главная развилка, на которой спотыкаются новички. Приёмник бывает двух видов:
| Вид приёмника | Синтаксис | Получает | Может менять оригинал? |
|---|---|---|---|
| Value receiver | (w Wallet) | Копию структуры | Нет |
| Pointer receiver | (w *Wallet) | Указатель на оригинал | Да |
Добавьте в тот же файл метод с pointer receiver:
// Deposit добавляет сумму к балансу.
func (w *Wallet) Deposit(kopecks int) {
w.BalanceKopecks += kopecks
}
А в main вызовите:
my.Deposit(50_00)
my.DisplayBalance()
Проверка: вывод — Баланс: 200.00 руб.. Go автоматически берёт адрес переменной my при вызове метода с pointer receiver, поэтому писать (&my).Deposit() не нужно.
Попробуйте заменить *Wallet на Wallet в Deposit, снова запустите — баланс останется 150.00. Метод получил копию, изменил её и выбросил. Это именно тот баг, который встречается у каждого второго начинающего Go-разработчика.
Шаг 3. Методы на не-структурных типах
Методы можно привязать к любому именованному типу, определённому в вашем пакете, а не только к struct. Единственное ограничение: тип должен быть объявлен в том же пакете, где объявляется метод.
package main
import "fmt"
// Kopecks — псевдоним для int, представляющий сумму в копейках.
type Kopecks int
// Rubles возвращает сумму в рублях с копейками.
func (k Kopecks) Rubles() string {
return fmt.Sprintf("%d.%02d руб.", k/100, k%100)
}
func main() {
price := Kopecks(999_99)
fmt.Println(price.Rubles())
}
Проверка: вывод — 999.99 руб..
Добавить метод к встроенному типу int напрямую нельзя — компилятор выдаст cannot define new methods on non-local type int. Именно для этого и нужен свой именованный тип.
Шаг 4. Набор методов и интерфейсы — как это связано
В Go интерфейс удовлетворяется неявно: если у типа есть все методы интерфейса, он его реализует. Но набор методов (method set) у значения и указателя различается:
- Значение типа
Tимеет method set из методов с value receiver(t T). - Указатель
*Tимеет method set из методов с обоими видами приёмников —(t T)и(t *T).
Практическое следствие: если интерфейс требует метод с pointer receiver, передать в него значение (не указатель) не получится. Компилятор скажет что-то вроде Wallet does not implement Depositer (Deposit method has pointer receiver).
Правило большого пальца: если хотя бы один метод типа использует pointer receiver, делайте все методы с pointer receiver. Смешивание допустимо, но на практике путает и вас, и тех, кто читает код после вас.
Шаг 5. Nil-приёмник — ловушка, о которой молчат туториалы
Метод с pointer receiver можно вызвать на nil-указателе, и Go не запаникует автоматически. Паника случится только когда вы обратитесь к полю:
var w *Wallet // nil
w.Deposit(100) // runtime panic: nil pointer dereference внутри Deposit
Если вам нужна защита — проверяйте приёмник в начале метода:
func (w *Wallet) Deposit(kopecks int) {
if w == nil {
return // или возвращайте ошибку
}
w.BalanceKopecks += kopecks
}
Проверка: после добавления проверки вызов w.Deposit(100) на nil-указателе перестанет вызывать панику.
Если не получилось
cannot define new methods on non-local type— вы пытаетесь добавить метод к типу из чужого пакета (например,intилиhttp.Request). Создайте свой именованный тип-обёртку в своём пакете.invalid receiver type— приёмник должен быть именованным типом или указателем на именованный тип. Не может быть указателем на указатель (**T), интерфейсом или анонимным типом.- Метод «не видит» изменений — почти наверняка вы используете value receiver вместо pointer receiver. Замените
(w Wallet)на(w *Wallet). does not implement ... (method has pointer receiver)— вы передаёте значение туда, где нужен указатель. Передайте&walletвместоwallet.nil pointer dereference— переменная-указатель не была инициализирована. Проверьте, что объект создан через&Wallet{}илиnew(Wallet)перед вызовом метода.
Сноска. Method set (набор методов) — множество методов, доступных для значения определённого типа. В Go это понятие критично, потому что именно method set определяет, реализует ли тип интерфейс. У значения T и указателя *T наборы методов разные — и это источник большинства ошибок компиляции, связанных с интерфейсами.
Что дальше
Методы — фундамент для двух следующих шагов: интерфейсов и встраивания (embedding). Попробуйте объявить интерфейс с одним методом и убедитесь, что ваш тип его удовлетворяет без явного implements. Затем встройте одну структуру в другую и посмотрите, как «промотируются» методы вложенного типа. Документация по обоим темам — в разделе Effective Go на официальном сайте проекта.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: The Go Programming Language Specification — Method declarations, Effective Go — Methods
Частые вопросы
Чем метод в Go отличается от обычной функции?
Когда использовать value receiver, а когда pointer receiver?
Можно ли добавить метод к типу из стандартной библиотеки?
Что будет, если вызвать метод на nil-указателе?
У меня другая версия Go — код будет работать?
Что не сработает, если смешивать value и pointer receiver у одного типа?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.