Структуры и методы в Go: пошаговая инструкция для старта
Структуры и методы — основа пользовательских типов в Go. Разберём, как объявлять struct, привязывать к нему методы, работать с указателями и интерфейсами — от первого файла до рабочего примера за 30 минут.

Что понадобится до начала
Прежде чем писать первую структуру, убедитесь, что на машине стоит Go версии 1.21 или новее. Проверить версию можно командой go version в терминале. Если вывод начинается с go version go1.21 или выше — всё в порядке. Если Go не установлен, скачайте его с официального сайта и следуйте инструкции для вашей ОС.
Также вам нужен любой текстовый редактор. VS Code с расширением Go или GoLand от JetBrains дают подсветку, автодополнение и встроенный запуск — но подойдёт и Vim.
Создайте каталог проекта и инициализируйте модуль:
mkdir go-structs-demo
cd go-structs-demo
go mod init go-structs-demo
В каталоге появится файл go.mod. Это значит, что модуль готов к работе.
Шаг 1. Объявляем структуру
Структура в Go — составной тип, который группирует поля разных типов под одним именем. Аналог класса в других языках, но без наследования.
Создайте файл main.go и вставьте в него следующий код (Go 1.21+):
package main
import "fmt"
// Order описывает заказ в интернет-магазине.
type Order struct {
ID int
Product string
Quantity int
Price float64 // цена за единицу в рублях
}
func main() {
// Создаём экземпляр структуры с именованными полями
order := Order{
ID: 1,
Product: "Клавиатура",
Quantity: 2,
Price: 4500.0,
}
fmt.Printf("Заказ #%d: %s × %d, итого %.2f руб.\n",
order.ID, order.Product, order.Quantity,
float64(order.Quantity)*order.Price)
}
Запустите:
go run main.go
Проверка: в терминале должна появиться строка Заказ #1: Клавиатура × 2, итого 9000.00 руб.. Если видите ошибку компиляции — убедитесь, что каждое поле в литерале заканчивается запятой, включая последнее.
Нулевое значение структуры
Если создать переменную без инициализации — var o Order — все поля получат нулевые значения своих типов: 0 для чисел, пустая строка для string. В Go нет null для структур-значений, и это спасает от целого класса ошибок.
Шаг 2. Привязываем метод
Метод в Go — обычная функция с «получателем» (receiver), который указывает, к какому типу она привязана. Получатель записывается перед именем функции в скобках.
Добавьте в main.go метод Total:
// Total возвращает общую стоимость заказа.
func (o Order) Total() float64 {
return float64(o.Quantity) * o.Price
}
Теперь в main можно написать:
fmt.Printf("Итого: %.2f руб.\n", order.Total())
Проверка: вывод — Итого: 9000.00 руб..
Получатель o Order — это копия структуры. Метод работает с копией и не может изменить оригинал. Для чтения этого достаточно, но для изменения полей нужен указатель.
Шаг 3. Указатель в получателе — когда и зачем
Допустим, нужен метод, который применяет скидку к заказу:
// ApplyDiscount уменьшает цену на процент скидки.
// Получатель — указатель, чтобы изменения затронули оригинал.
func (o *Order) ApplyDiscount(percent float64) {
o.Price *= (1 - percent/100)
}
Вызов:
order.ApplyDiscount(10)
fmt.Printf("Цена после скидки: %.2f руб.\n", order.Price)
fmt.Printf("Итого: %.2f руб.\n", order.Total())
Проверка: цена станет 4050.00, итого — 8100.00.
Правило большого пальца в Go: если хотя бы один метод типа принимает указатель, делайте указателем все получатели этого типа. Смешивание value- и pointer-receiver на одном типе запутывает и мешает реализовывать интерфейсы.
Когда нужен указатель, а когда — значение
| Критерий | Получатель-значение | Получатель-указатель |
|---|---|---|
| Метод меняет поля | Нет — работает с копией | Да — меняет оригинал |
| Структура большая (много полей, слайсы, карты) | Копирование дорого | Передаётся адрес — дёшево |
| Нужна совместимость с интерфейсом | Реализует и значение, и указатель | Реализует только указатель |
| Структура мала и иммутабельна (координаты, цвет) | Идеальный случай | Лишний уровень косвенности |
Шаг 4. Встраивание вместо наследования
Go не поддерживает наследование классов. Вместо него — встраивание (embedding): одна структура включает другую как анонимное поле, и все методы «всплывают» наверх.
// Customer — данные покупателя.
type Customer struct {
Name string
Email string
}
// CustomerOrder расширяет Order данными покупателя.
type CustomerOrder struct {
Order // встроенная структура
Customer // ещё одна встроенная структура
}
func main() {
co := CustomerOrder{
Order: Order{
ID: 2,
Product: "Монитор",
Quantity: 1,
Price: 32000,
},
Customer: Customer{
Name: "Алексей",
Email: "alex@example.com",
},
}
// Total() «всплыл» из Order
fmt.Printf("%s, итого: %.2f руб.\n", co.Name, co.Total())
}
Проверка: Алексей, итого: 32000.00 руб.. Метод Total вызывается прямо на co, без обращения к полю co.Order.Total().
Шаг 5. Интерфейсы — связующее звено
Интерфейс в Go — набор сигнатур методов. Тип реализует интерфейс автоматически, если у него есть все перечисленные методы. Ключевого слова implements нет.
// Pricer — всё, что умеет посчитать общую стоимость.
type Pricer interface {
Total() float64
}
// printTotal принимает любой тип, реализующий Pricer.
func printTotal(p Pricer) {
fmt.Printf("Стоимость: %.2f руб.\n", p.Total())
}
Вызов printTotal(order) и printTotal(co) сработает для обоих типов, потому что у каждого есть метод Total() float64.
Проверка: компилятор не жалуется, функция выводит числа. Если убрать метод Total из Order, компиляция упадёт с сообщением вида Order does not implement Pricer (missing method Total).
Сноска. Receiver (получатель) — параметр перед именем метода в Go, который привязывает функцию к конкретному типу. Синтаксис: func (переменная Тип) Имя(). Аналог this / self в других языках, но в Go имя получателя выбираете вы сами — по соглашению это одна-две буквы от имени типа.
Если не получилось
syntax error: unexpected newline, expecting comma or }— забыли запятую после последнего поля в литерале структуры. В Go запятая обязательна, даже если закрывающая фигурная скобка на следующей строке.cannot use order (variable of type Order) as type Pricer— методTotalобъявлен на указателе (*Order), а переменная — значение. Либо сделайте получатель значением, либо передавайте&order.undefined: Order— метод объявлен в другом файле, но вы запускаетеgo run main.goвместоgo run .. Командаgo run .компилирует все файлы пакета в текущем каталоге.ambiguous selector co.ID— две встроенные структуры имеют поле с одинаковым именем. Обращайтесь явно:co.Order.ID.cannot take address of Order{...}— вы пытаетесь вызвать pointer-receiver метод на литерале без присваивания. Сначала сохраните литерал в переменную, потом вызывайте метод.
Что делать дальше
Структуры и методы — фундамент, но одного фундамента мало. Следующие шаги:
- Разобраться с интерфейсами стандартной библиотеки:
fmt.Stringer,io.Reader,io.Writer,error. РеализацияStringer— хороший первый интерфейс для практики: добавьте методString() stringкOrderи посмотрите, какfmt.Printlnначнёт использовать ваш формат. - Попробовать композицию через несколько уровней встраивания и убедиться, что конфликты имён — реальная проблема, а не теоретическая.
- Написать юнит-тест на метод
Total: создайте файлmain_test.goи запуститеgo test ./.... Тестирование в Go встроено в язык — отдельных фреймворков не нужно. - Прочитать раздел Effective Go — Methods в официальной документации: там описаны соглашения по именованию получателей и выбору между значением и указателем.
Создатель презентаций: Структура и текст каждого слайда: заголовки-утверждения, одна мысль на слайд, заметки докладчику.
Спросить за 150 ₽Источники: Effective Go — Methods (официальная документация Go), A Tour of Go — Structs and Methods
Частые вопросы
Чем структура в Go отличается от класса в Python или Java?
Можно ли создать конструктор для структуры?
Когда выбирать получатель-указатель, а когда значение?
Что будет, если у двух встроенных структур совпадают имена полей?
Я использую Go 1.18 — примеры из статьи будут работать?
Как понять, реализует ли мой тип нужный интерфейс?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.