Олег Марков
Руководство по embed в Go
Введение
Иногда при разработке на Go возникает задача, когда к приложению нужно подключить дополнительные файлы: статические страницы, изображения, шаблоны для генерации HTML, конфиги и многое другое. Такие файлы важно обеспечить прямо внутри итоговой программы, чтобы не раздавать их отдельными файлами вместе с бинарником и не создавать лишних зависимостей от внешних ресурсов. Это особенно актуально, если вы хотите упростить деплой — всё должно запускаться как единое целое.
До выхода Go 1.16 задача решалась сторонними инструментами — такими как go-bindata, packr и аналогичными утилитами. Теперь с помощью стандартной библиотеки вы просто добавляете файлы в само приложение через пакет embed
, и никаких дополнительных шагов не требуется.
В этой статье вы познакомитесь с возможностями пакета embed
. Я покажу вам, как он работает, какие задачи решает, покажу примеры кода и дам объяснения важнейших моментов — от указания файлов до извлечения содержимого во время выполнения приложения.
Как работает embed в Go
Пакет embed
позволяет встраивать (embed — встраивать, внедрять) содержимое файлов (или даже целых директорий) непосредственно в финальный бинарник вашего приложения. Это значит, что после компиляции все необходимые ресурсы будут частью исполняемого файла, и получить их можно будет с помощью переменных, определённых в Go-коде.
Чтобы начать пользоваться возможностями embed, вам нужно:
- Подключить пакет embed (даже если вы не используете функции из него явно).
- Объявить переменную одного из специальных типов —
string
,[]byte
илиembed.FS
. - Использовать директиву
//go:embed
, чтобы указать, какие файлы встраивать.
Давайте разберёмся, как это выглядит на практике.
Подключение пакета
Импорт пакета обязательно, даже если обращаться к нему напрямую не планируется:
import _ "embed" // Импорируем, даже если не обращаемся к функциям пакета напрямую
Это предотвращает ошибку компиляции, если вы используете директивы go:embed
.
Какие типы переменных подходят
Директива //go:embed
работает только с переменными следующих типов:
- string
- []byte
- embed.FS
С помощью string
и []byte
можно встраивать только один файл. Если вы хотите встроить несколько файлов или целую директорию, используйте embed.FS
.
Простейший пример: встраиваем текстовый файл в строку
Давайте посмотрим, как можно встроить в бинарник текстовый файл:
Пример файла
Пусть у нас есть файл с именем message.txt
:
Привет, мир!
Пример встраивания
package main
import (
_ "embed"
"fmt"
)
//go:embed message.txt
var message string // Переменная должна быть объявлена сразу после go:embed
func main() {
fmt.Println(message) // Выводит содержимое файла message.txt
}
Комментарии в коде обращают внимание на важный момент: объявление переменной обязательно должно следовать непосредственно за директивой //go:embed
. В противном случае компилятор выдаст ошибку.
Встраивание файла в []byte
Если содержимое файла двоичное, стоит использовать тип []byte
:
package main
import (
_ "embed"
"fmt"
)
//go:embed image.png
var imageData []byte // Здесь хранятся байты из файла image.png
func main() {
fmt.Printf("Размер файла: %d байт\n", len(imageData))
// Теперь с imageData можно работать как с байтовым массивом
}
В этом примере вы увидите, как считать размер встроенного изображения.
Встраивание нескольких файлов или директорий (embed.FS)
В случаях, когда нужно встроить несколько ресурсов, обращайтесь к типу embed.FS
. Он предоставляет виртуальное файловое дерево только для чтения, которое можно использовать как обычное дерево файлов.
Пример структуры проекта
project/
├── templates/
│ ├── main.html
│ └── sidebar.html
└── main.go
Пример кода
package main
import (
"embed"
"fmt"
"io/fs"
)
//go:embed templates/*
var templatesFS embed.FS // Храним все файлы из папки templates
func main() {
files, _ := fs.Glob(templatesFS, "templates/*.html") // Получаем список файлов шаблонов
for _, file := range files {
data, _ := templatesFS.ReadFile(file)
fmt.Printf("%s:\n%s\n---\n", file, string(data))
}
}
Здесь используется функция fs.Glob
для поиска файлов по шаблону, а затем ReadFile
, чтобы прочесть содержимое каждого шаблона.
Паттерны и маски для go:embed
Вы можете указывать:
- Отдельные файлы:
//go:embed file.txt
- Директории:
//go:embed static/*
- Несколько путей через пробел:
//go:embed a.txt b.txt c/*.json
Поддерживаются маски (*
, ?
) и относительные пути относительно файла Go-кода, где размещена директива.
Ограничения и особенности пакета embed
Вот нюансы, на которые стоит обратить внимание:
- Встраиваемые файлы определяются во время компиляции. То есть, если вы после компиляции поменяете embedded-файлы на диске, внутри бинарника они останутся прежними до следующей сборки.
embed
работает только с файлами, доступными на момент сборки. Не поддерживается загрузка файлов из интернета или по абсолютным путям.- Файлы нельзя изменять из программы во время её исполнения — только читать.
- Файлы игнорируются, если их нет по указанному пути во время компиляции (ошибка компиляции).
- Путь в директиве всегда относительно файла, где размещён
//go:embed
. Если структура проекта становится сложной — этот момент часто становится источником ошибок. - Встроенные файлы не видны в исходном дереве файлов вашей операционной системы. С ними можно работать только через ваши переменные внутри кода.
Взаимодействие с файловой системой
Встроенное дерево файлов через embed.FS
соответствует интерфейсу fs.FS
, который стандартный для доступа к абстрактным файловым источникам в Go. Это значит, что функции, которые работают с виртуальными файловыми системами (например, http.FileServer
, template.ParseFS
), запросто принимают на вход embed.FS
.
Вот пример, как встроить статические файлы и раздать их через HTTP:
package main
import (
"embed"
"net/http"
)
//go:embed static/*
var staticFS embed.FS
func main() {
fs := http.FS(staticFS) // Оборачиваем embed.FS в http.Filesystem
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(fs)))
http.ListenAndServe(":8080", nil) // Сервер будет доступен на http://localhost:8080/static/
}
Теперь содержимое папки static
будет раздаваться пользователям по URL http://localhost:8080/static/
.
Использование шаблонов (text/template и html/template)
Многие Go-приложения работают с шаблонами, которые раньше лежали на диске. С внедрением embed
стало проще держать шаблоны вместе с бинарником.
Пример:
package main
import (
"embed"
"html/template"
"os"
)
//go:embed templates/*
var templatesFS embed.FS
func main() {
// Загружаем все шаблоны из встроенной папки templates
tmpl, err := template.ParseFS(templatesFS, "templates/*.html")
if err != nil {
panic(err)
}
data := struct{ Name string }{Name: "Мир"}
tmpl.ExecuteTemplate(os.Stdout, "main.html", data)
}
Этот подход работает и с text/template
.
Особенности использования в тестах
Если вы планируете использовать embed в тестах (например, в тестовых данных), структура та же — просто убедитесь, что файлы доступны там, куда ссылается ваш embed-путь.
Совет: стоит хранить тестовые ресурсы в папке рядом с тестовыми файлами, чтобы не было путаницы с путями.
Пример
//go:embed testdata/input.json
var inputData string
Использование относительных и абсолютных путей
embed
строго требует относительных путей относительно файла с директивой. Абсолютные пути не поддерживаются. Это сделано, чтобы не возникало разночтений и чтобы проекты легко переносились между разработчиками и разными машинами.
Встраивание файлов с учётом gitignore
embed
не учитывает содержимое .gitignore
, т.е. можно встроить даже файлы, которые не залиты в git. Но если файл отсутствует физически на момент билда, возникнет ошибка.
Также обратите внимание: встроенные файлы увеличивают размер итогового бинарника, что важно учитывать для дистрибуции на ограниченные среды.
Совместимость с кросс-платформенными и кросс-компиляторными сборками
Поскольку embed работает на этапе компиляции, дополнительные настройки для кросс-компиляции не требуются — файлы автоматически включаются в бинарник для нужной платформы.
Заключение
Пакет embed
— очень мощный и удобный инструмент для встраивания файловых ресурсов прямо в исполняемый binary-файл на Go. Это решение избавляет от многих типичных проблем с доставкой вспомогательных файлов, минимизирует потребность во внешних зависимостях и делает деплой приложений заметно проще. Теперь вы знаете основные приемы и способы использования, умеете встраивать как одиночные файлы, так и целые директории, понимаете ограничения и правила работы.
Дальнейшее использование embed
сводится лишь к аккуратному управлению вашими ресурсами и выбору корректных путей.
Частозадаваемые технические вопросы по теме статьи и ответы на них
Как встроить файлы, которые лежат в разных папках проекта?
В одной директиве можно перечислить несколько вариантов путей через пробел, например:
go
//go:embed templates/* configs/*.json
Главное — все пути должны быть относительными к файлу, где размещается директива.
Как обновить встроенные файлы, если исходники изменились?
Вам нужно пересобрать бинарник. После изменений файлов выполните новую сборку с помощью go build
.
Можно ли получить список всех файлов, встроенных через embed.FS?
Да, через функцию ReadDir
:
go
dirs, err := templatesFS.ReadDir("templates")
for _, entry := range dirs {
fmt.Println(entry.Name())
}
Это покажет имена всех файлов и папок внутри папки templates
.
Как встроить файл с названием, начинающимся с точки (например, ".env")?
Такие файлы могут быть пропущены, если явно не указаны. Указывайте их напрямую:
go
//go:embed .env
Как определить MIME-тип или тип содержимого встроенного файла?
Go стандартно не хранит MIME-тип, но тип можно определить вручную, используя сторонние библиотеки, например, net/http
для функции DetectContentType
:
go
mimeType := http.DetectContentType(imageData)
Где imageData
— содержимое файла в виде []byte.
Постройте личный план изучения Golang до уровня Middle — бесплатно!
Golang — часть карты развития Backend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Golang
Лучшие курсы по теме

Основы Golang
Антон Ларичев
Nest.js с нуля
Антон Ларичев