Зарегистрируйтесь, чтобы продолжить обучение

Встраивание структур Структуры в Go

В Go структуры часто становятся основным способом описывать данные и сущности программы. Но иногда хочется не просто описать поля, а взять готовую структуру и встроить ее внутрь другой, чтобы использовать ее как часть новой сущности. Это называется встраивание структур (struct embedding).

Простая аналогия

Представим, что у нас есть паспорт. Паспорт содержит поля: номер, серия, дата выдачи. Но паспорт всегда связан с человеком. Вместо того чтобы каждый раз описывать эти поля у человека, мы можем сказать: у человека есть паспорт и встроить структуру Passport внутрь структуры Person.

Базовый пример

Начнем с простого кода:

package main

import "fmt"

type Passport struct {
    Number string
    Issued string
}

type Person struct {
    Name     string
    Age      int
    Passport // встраиваем структуру
}

func main() {
    p := Person{
        Name: "Иван",
        Age:  30,
        Passport: Passport{
            Number: "1234 567890",
            Issued: "Москва",
        },
    }

    // благодаря встраиванию мы можем обращаться к полям паспорта напрямую
    fmt.Println(p.Name)   // Иван
    fmt.Println(p.Number) // 1234 567890
    fmt.Println(p.Issued) // Москва
}

Здесь видно, что у Person нет явных полей Number и Issued, но так как у него встроена структура Passport, мы можем обращаться к этим полям как будто они у Person.

Это и есть главное удобство — доступ к полям вложенной структуры становится прямым, без лишнего кода.

Когда встраивание помогает

Чаще всего встраивание используют для того, чтобы расширять существующие структуры.

Например, у нас есть структура User, описывающая пользователя в системе. Для администратора хочется добавить права доступа, но не дублировать все заново.

type User struct {
    ID   int
    Name string
}

type Admin struct {
    User   // встраивание
    Rights []string
}

func main() {
    a := Admin{
        User: User{
            ID:   1,
            Name: "Мария",
        },
        Rights: []string{"create", "delete"},
    }

    // можем обращаться к полям User напрямую

    fmt.Println(a.ID)   // 1
    fmt.Println(a.Name) // Мария
    fmt.Println(a.Rights)
}

Здесь Admin — это как будто «расширенный User». Мы не повторяем поля, а встраиваем User.

Встраивание и методы

Фишка в том, что вместе с полями встраиваемой структуры "подтягиваются" и ее методы.

type Logger struct{}

func (l Logger) Log(message string) {
    fmt.Println("LOG:", message)
}

type Service struct {
    Name   string
    Logger // встраиваем логгер

}

func main() {
    s := Service{Name: "OrderService"}
    s.Log("Запущен сервис") // метод Logger стал методом Service
}

Так можно "наследовать" поведение от другой структуры. Это похоже на наследование в ООП, но в Go нет классов и строгой иерархии, поэтому это скорее композиция с бонусом доступа к методам.

Что делать, если имена совпадают?

Иногда у встраиваемой структуры и у внешней есть одинаковые поля или методы. Тогда возникает "конфликт".

type Base struct {
    Name string
}

type Extended struct {
    Name string
    Base
}

func main() {
    e := Extended{
        Name: "Внешнее имя",
        Base: Base{Name: "Встроенное имя"},
    }

    fmt.Println(e.Name)      // Внешнее имя (приоритет у внешнего поля)
    fmt.Println(e.Base.Name) // доступ к полю Base
}

Внешние поля всегда приоритетнее. Чтобы обратиться к полям или методам вложенной структуры, используем явное указание (e.Base.Name).

Когда лучше не использовать встраивание

Хотя встраивание очень удобно, оно может запутывать. Представь структуру с 3–4 встроенными типами, у которых еще и совпадающие методы. Понять, откуда вызвался метод, становится сложно.

Лучше использовать встраивание, когда:

  • это реально общая часть, которую удобно расширять,
  • количество встроенных структур небольшое,
  • нет риска сильных конфликтов имен.

Если же нужно просто "составить" объект из частей, без магии доступа к методам и полям напрямую, часто лучше сделать обычное поле:

type Engine struct {
    HorsePower int
}

type Car struct {
    Engine Engine // обычное поле
}

Так код более явный, и читатель сразу понимает, что у машины есть двигатель, а не что двигатель сам по себе "живет" внутри машины.

Встраивание структур в Go — это удобный способ собирать сложные сущности из простых деталей. Оно помогает:

  • расширять структуры без дублей кода,
  • автоматически получать доступ к методам и полям встроенных типов,
  • строить более гибкую и компактную архитектуру.

Но у этого подхода есть обратная сторона. Если переборщить со встраиваниями, код становится туманным: непонятно, откуда взялось, то или иное поле, или метод. В таких случаях лучше остановиться и сделать обычное явное поле.

Главное правило: встраивание хорошо там, где оно делает код чище и короче, а не запутаннее.


Самостоятельная работа

Встраивание позволяет собирать составные сущности и обращаться к полям вложенной структуры напрямую. Закрепим это на коротком примере.

Задачи:

  1. Опишите структуру Address с полями: City string, Street string.
  2. Опишите структуру User с полями: Name string, Age int и встроенной структурой Address (без имени поля).
  3. Создайте User и выведите все поля; к City и Street обращайтесь напрямую, например u.City.
Показать пример ответа
package main

import "fmt"

type Address struct {
    City   string
    Street string
}

type User struct {
    Name    string
    Age     int
    Address // встраивание
}

func main() {
    u := User{
        Name: "Анна",
        Age:  28,
        Address: Address{
            City:   "Москва",
            Street: "Ленина, 10",
        },
    }

    fmt.Printf("%s (%d) — %s, %s\n", u.Name, u.Age, u.City, u.Street)
}

Дополнительные материалы

  1. Effective Go — Embedding
  2. Go Spec — Struct types (embedded fields)
  3. Go Spec — Method sets

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff