Структуры в Go
Теория: Поля структуры и их типы
В Go структура — это способ собрать связанные данные в единый объект. Каждый элемент внутри структуры называется полем. Поле имеет имя и тип, и именно это делает работу с данными безопасной и предсказуемой.
Проблема без структур
Представим, что мы пишем систему блогов. Если не использовать структуры, данные приходится хранить в отдельных срезах и синхронизировать их по индексам:
Здесь мы пытаемся собрать информацию о постах, но она разнесена по разным срезам. Нужно постоянно помнить индексы: если где-то ошибиться, данные «поедут». Например, пост с названием «Go vs Python» может оказаться у Марии и с неправильным количеством лайков. Это типичный источник ошибок.
Решение с помощью структур
Опишем сущность поста как структуру:
Теперь мы явно говорим: у каждого поста есть ID, заголовок, автор и лайки. Создадим список постов:
Мы собрали все данные о посте в одном месте. Теперь, чтобы получить автора первого поста, достаточно обратиться к posts[0].Author. Код стал проще и надёжнее.
Примеры полей и их типов
Поля могут быть разных типов. Например, товар в интернет-магазине:
Здесь мы описали сущность продукта: у него есть название, цена, количество и набор тегов. Теги хранятся в срезе, потому что их может быть разное количество.
Более сложная структура — корзина:
В Cart мы связали сразу несколько сущностей: владелец корзины, список товаров и карта с промокодами. Это удобно: теперь корзина — это полноценный объект с данными.
Практический кейс
До структур: в сервисе доставки еда, ресторан, список блюд и адреса клиентов хранились в отдельных срезах.
Чтобы понять, куда доставить конкретный заказ, приходилось связывать несколько срезов через индексы. Ошибки были постоянные: пиццу отправляли не по тому адресу, или заказ терялся.
После структур: всё собрано в объект Delivery.
Теперь каждая доставка — это единый объект. Чтобы узнать адрес первой доставки, мы пишем deliveries[0].Address. Ошибиться невозможно: данные связаны внутри структуры.
Экспортируемые и приватные поля
В Go видимость поля определяется первой буквой его имени:
- Заглавная (
ID,Name) — экспортируемое поле, доступно из других пакетов. - Строчная (
balance,passwordHash) — приватное поле, доступно только внутри текущего пакета.
Это не «инкапсуляция по рантайму», а правило компилятора: другие пакеты просто не могут обратиться к приватному имени.
Базовый пример
Что сделали и зачем: скрыли balance, чтобы запретить прямое присваивание снаружи и обеспечить инварианты (нельзя сделать отрицательное пополнение). Внешний код видит только безопасные операции.
Экспорт/приватность и JSON/YAML, ORM, рефлексия
Большинство пакетов, работающих через рефлексию (encoding/json, yaml, mapstructure, ORM/библиотеки), видят только экспортируемые поля. Приватные будут проигнорированы, даже если написали теги.
Вывод: если нужно (де)сериализовать поле — делайте его экспортируемым и управляйте именем через теги (json:"field_name,omitempty").
Паттерн: «умный конструктор» + приватные поля
Скрываем детали, отдаём только валидные объекты.
Зачем: запрещаем создавать «полупустых» пользователей и хранить сырой пароль. Внешний код не сможет случайно прочитать или записать passwordHash.
Паттерн: публичный DTO + приватная доменная модель
Разделяем типы для API и для доменной логики.
Как это работает: внутри пакета свободно оперируем приватными полями и инвариантами; наружу отдаём «чистый» DTO.
Встраивание (embedding) и видимость
При встраивании экспортируемые поля/методы «продвигаются» на уровень выше и доступны, если они экспортируемые.
Зачем: повторно используем общие поля (ID, Audit) без дублирования, при этом приватные детали остаются скрыты.
Тестирование и границы пакетов
- Тесты в том же пакете (
package x) видят приватные поля. - В «внешнем» стиле (
package x_test) приватные поля не видны — тестируйте через публичное API.
Это мотивирует проектировать удобные публичные методы и не «протаскивать» приватные детали наружу ради тестов.
Экспорт методов на приватных типах
Можно объявить экспортируемый метод на приватном типе, но тип всё равно нельзя использовать извне, так как его имя недоступно.
Практически это мало полезно: извне не получится создать secret.
Нейминг и стиль
Следуйте стандартам Go:
- Экспортируемые имена — понятные, без префиксов
Get/Set, если не требуется. - Акронимы полностью капсом:
HTTPServer,URL,ID(а неHttpServer,Id).
Производительность
Экспорт/приватность не влияют на скорость доступа на рантайме. Это исключительно правило компилятора.
Мини-кейсы
1) Конфиг сервиса
Зачем: не позволяем собрать невалидный конфиг, наружу отдаём только безопасный Addr().
2) Модель для БД и для API
Как работает: доменная модель может меняться, API — стабильное. Приватные даты не «протекают» в ответ.
3) Безопасные деньги
Зачем: снаружи нельзя сделать Amount{cents: -1} или хранить дроби с плавающей точкой.
4) Иммутабельность по договорённости
Пояснение: закрытый тип + копирование среза защищают от внешних изменений исходного массива.
Итог
Поля структуры позволяют описывать реальные объекты системы так, как они есть: пост, продукт, доставка, аккаунт. Вместо разрозненных переменных у нас появляются целостные сущности. Примеры показывают: когда мы объясняем, что именно сделали (собрали данные в объект) и зачем (избежать ошибок и сделать код понятным), становится ясно, как работает структура. Она делает код надёжным, безопасным и расширяемым.
В Go нет отдельного «специального синтаксиса» для экспорта полей. Всё работает очень просто:
- Заглавная буква в имени поля (или метода, функции, типа, константы, переменной) → символ экспортируемый (виден из других пакетов).
- Строчная буква → символ приватный (виден только внутри текущего пакета).
Это общее правило для всего Go, не только для структур.
Примеры:
Здесь нет никаких модификаторов (public, private, protected), как в Java или C#.
Всё регулируется только первой буквой имени.


