Объявление и создание структуры
Тип структуры это список именованных полей. Он объявляется через type Name struct { ... }, а значения создаются составным литералом.
Вывод:
Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}
%+v печатает имена полей, а %#v синтаксис Go. Оба варианта это самый быстрый способ посмотреть на структуру при отладке.
Поля одного типа можно записать в одну строку: X, Y float64. Поля с заглавной буквы экспортируются (видны другим пакетам и encoding/json), со строчной нет.
Формы литералов
User{Name: "Ana", Age: 31} // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"} // pointer to a new struct
new(User) // pointer to a zeroed struct
Предпочитайте именованные поля. Позиционные литералы ломаются, когда кто-то добавляет или переставляет поле, и go vet предупреждает о них для типов структур из других пакетов (например, net/url.Error struct literal uses unkeyed fields). Для крошечных локальных типов вроде Point{1, 2} позиционная форма допустима.
Значений по умолчанию нет: нулевые значения и конструкторы
В Go нет значений полей по умолчанию и нет конструкторов на уровне языка. Каждое поле начинается с нулевого значения своего типа: 0, "", false, nil. Пробел закрывают две идиомы.
Сделайте нулевое значение полезным. sync.Mutex, bytes.Buffer и strings.Builder работают без инициализации. Если тип можно спроектировать так, сделайте это.
Напишите функцию NewX, когда полю нужно ненулевое значение по умолчанию или что-то нужно выделить, например мапу:
NewServer возвращает указатель, потому что у сервера есть методы, которые его меняют, и он будет общим. Возвращать структуру по значению тоже принято для маленьких неизменяемых типов (time.Date возвращает time.Time).
Указатели на структуры
При обращении к полям Go разыменовывает указатели на структуры автоматически. Для p := &User{} пишут p.Name, а не (*p).Name.
Вывод:
{11 12}
{11 12} {0 12}
{11 0}
Структуры это значения. Присваивание и передача в функцию их копируют, а указатель позволяет работать с одной и той же структурой. Копирование по полям поверхностное: поле-слайс или поле-мапа в копии указывает на те же данные. Когда что использовать, объясняет страница об указателях.
Анонимные структуры
Тип структуры можно записать прямо на месте, без имени. Это удобно для разовых группировок, табличных тестов и декодирования JSON, который читается один раз.
Таблица []struct{ in string; want int } это стандартная форма теста на Go.
Сравнение структур
Структуры сравнимы через ==, когда сравнимы все их поля. Равенство означает, что равны все поля. Сравнимые структуры также могут быть ключами мапы.
Для структуры с полями-слайсами или мапами напишите метод Equal или сравнивайте в тестах через reflect.DeepEqual.
Теги структур
Тег это строковый литерал после типа поля. Язык его игнорирует, а библиотеки читают через рефлексию. Самый частый читатель это encoding/json:
Программа печатает {"id":7,"name":"Mug"}: тег переименовывает поля, omitempty отбрасывает нулевой Price, а неэкспортированное поле кодировщик не видит. Теги следуют соглашению key:"value", несколько ключей разделяются пробелами: `json:"name" db:"user_name"`. Некорректный тег компилируется без ошибок и молча игнорируется, но go vet его ловит. Опции описаны на странице о json.
Пустые структуры и порядок полей
У struct{} нет полей, и она занимает ноль байт. Её используют как значение множества (map[string]struct{}) и как сигнал в каналах (chan struct{}), где важен только сам факт события.
Порядок полей влияет на размещение в памяти: компилятор выравнивает поля с помощью заполнения. Структура из bool, int64, bool занимает 24 байта на 64-битных системах, а int64, bool, bool занимает 16. Это важно только для типов, которые хранятся в больших количествах.
Частые ошибки
- Изменение копии.
for _, u := range users { u.Age++ }каждый раз меняет копию. Используйте индекс:users[i].Age++. - Неинициализированное поле-мапа. Нулевое значение структуры содержит nil-мапу; запись в неё вызывает панику. Выделяйте её в конструкторе.
- Копирование структуры с
sync.Mutex. У копии своё состояние блокировки. Передавайте такие структуры по указателю;go vetсообщит о копировании. - Позиционные литералы для типов из других пакетов. Они ломаются при добавлении поля.
Часто задаваемые вопросы
Как инициализировать структуру в Go?
Составным литералом. Обычная форма с именованными полями: u := User{Name: "Ana", Age: 31}, и любое пропущенное поле получит нулевое значение. User{} даёт все нулевые значения, &User{...} даёт указатель, а new(User) даёт указатель на обнулённую структуру.
Есть ли в Go значения по умолчанию для полей структуры?
Нет. Каждое поле начинается с нулевого значения своего типа. Идиома: функция-конструктор func NewServer() *Server { return &Server{Port: 8080} } или проектирование типа так, чтобы его нулевое значение уже было полезным, как у sync.Mutex и bytes.Buffer.
Как сравнить две структуры в Go?
Через ==, если все поля сравнимы. Два значения структуры равны, когда равны все соответствующие поля. Структуру с полем-слайсом, мапой или функцией нельзя сравнить через == (ошибка компиляции); сравнивайте поля сами или используйте reflect.DeepEqual в тестах.
Что такое теги структур в Go?
Строковые литералы после типа поля, например `json:"name,omitempty"`. Компилятор их игнорирует; пакеты вроде encoding/json, драйверов баз данных и валидаторов читают их через рефлексию, чтобы управлять тем, как поля кодируются, называются или проверяются.
Как вывести структуру с именами полей в Go?
Используйте fmt.Printf("%+v\n", s), который печатает {Name:Ana Age:31}. %v печатает только значения, а %#v выводит синтаксис Go вместе с именем типа: main.User{Name:"Ana", Age:31}.