До 12.5 млн токенов на тест ИИ-агента
Вход/ Регистрация

Как использовать шаблоны в Go

Миша Курушин
Миша Курушин
Технический писатель
09 ноября 2023 г., обновлено 07 октября 2026
5602
13 минут чтения

Golang на «своем борту» несет мощный универсальный шаблонизатор, позволяющий динамически формировать вывод как текстовой информации (например, электронное письмо, документ или просто консольная команда), так и целых веб-страниц.

Шаблонизация в Go состоит из двух базовых пакетов — у каждого свое предназначение:

  • text/template
  • html/template

Важно отметить, что оба пакета имеют абсолютно идентичный интерфейс, однако второй автоматически защищает вывод HTML от определенных видов атак — например, от injections.

Преобразование Go-шаблона в итоговый вывод выполняется за счет применения к шаблону соответствующей структуры данных. Входной текст для Golang шаблона представляется в любом формате в кодировке UTF-8.

Сущности шаблона

Используемый шаблон, как правило, привязывается к определенной структуре данных (например, struct), данные из которой будут появляться внутри шаблона.

Поэтому, любой шаблон формально состоит 3 типов базовых сущностей, которые «вынимают» из него нужные переменные и подставляют в вывод.

Действия (Actions)

Это фрагменты текста, заключенные в фигурные скобки {{ }}, в которых выполняется вычисление или подстановка некоторых данных. Именно за счет них текст внутри шаблона по своему наполнению становится динамическим.

Действиями можно считать как простую подстановку переменной, так и выполнение циклов или функций, который формируют итоговый текст. Все они непосредственно управляют тем, как будет выглядеть окончательный результат.

Условия (Conditions)

К условиям относятся классические конструкции if-else, которые используются непосредственно внутри шаблона. Благодаря условиям можно добавлять или убирать из конечного вывода целые текстовые блоки, что существенно увеличивает возможности шаблонизации и гибкость генерации контента.

Циклы (Loops)

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

Управление шаблонами

Непосредственно в самом Go есть 3 наиболее часто используемые функции для управления шаблонами:

  • New. Создает новый шаблон, который впоследствии нужно будет определить.
  • Parse. Анализирует переданную строку, содержащую текст шаблона, после чего возвращает уже готовый к использованию шаблон.
  • Execute. Выполняет готовый шаблон (который прошел этап парсинга), применяя к нему указанную структуру данных, после чего записывает результат в заданную переменную

Кстати, еще существует функция ParseFiles для обработки не строки с содержимым шаблона, а целых файлов.

Следующий код демонстрирует, как использование этих базовых шаблонных функций выглядит в самом тривиальном случае:

    
package main // помимо шаблонизатора подключаем os, который предоставляет независимый от платформы интерфейс для взаимодействия с операционной системой. В нашем случае он по понадобиться для вывода результата выполнения шаблона в консоль import (     "os"     "text/template" ) // определяем структуру, данные которой будут подставляться в шаблон type Person struct {     Name string     Age int } func main() {     some_person := Person{"Alex", 32} // экземпляр ранее определенной структуры     some_template := "This is {{ .Name }} and he is {{ .Age }} years old" // текстовое содержимое самого шаблона с проставленными внутри действиями (actions), заключенными в фигурные скобки     // создаем новый шаблон и парсим его содержимое, тем самым подготавливая его к дальнейшему использованию     ready_template, err := template.New("test").Parse(some_template)     // проверяем на наличие ошибок (nil означает ничего, как null в C)     if err != nil {         panic(err) // останавливаем выполнение и выводим содержимое ошибки     }     // выполняем шаблон и выводим его в консоль приложения     err = ready_template.Execute(os.Stdout, some_person) // ВЫВОД: This is Alex and he is 32 years old     // снова проверяем на наличие ошибок     if err != nil {         panic(err) // останавливаем выполнение и выводим содержимое ошибки     } }

«Скомпилированный» с помощью функции Parse шаблон можно использовать повторно, но уже с данными из другой структуры. Например, вы могли бы продолжить функцию main из кода выше:

    
// здесь идет ранее написанный код ...     another_person := Person{"Maks", 27} // создаем другой экземпляр структуры     err = ready_template.Execute(os.Stdout, another_person) }

Обратите внимание, что внутри шаблона в фигурных скобках указываются переменные структуры, которая была передана в шаблон при его выполнении. При этом имя переменной указывается через точку, а название самой структуры как бы опускается:

    
This is {{ .Name }} and he is {{ .Age }} years old

Кстати, внутри шаблона можно напрямую обратиться к данным, которые были переданы при выполнении:

    
package main import (   "os"     "text/template" ) func main() {     some_template := "Тут обошлось {{ . }}"     ready_template, err := template.New("test").Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, "без данных, просто текст") // ВЫВОД: Тут обошлось без данных, просто текст }

Особенности синтаксиса шаблонов

Статический текст

В самом трививальном случае шаблон может просто выводить статичный текст не используя никаких дополнительных данных:

    
import (     "os"     "text/template" ) ... some_template := "Просто обычный текст" ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Просто обычный текст

Статический текст внутри «Действия» (фигурных скобок)

Обычный статический текст можно усложнить дополнительными данными:

    
import (     "os"     "text/template" ) ... some_template := "Не совсем обычный текст с {{ \“дополнительными\” }} данными" // не забываем также экранировать двойные кавычки ready_template, err := template.New("test").Parse(some_template) if err != nil { panic(err) } ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Не совсем обычный текст с дополнительными данными

Кстати, можно дополнительно перед фигурными скобками и после них указывать маркеры обрезки:

    
... some_template := "Не совсем обычный текст с {{- \“дополнительными\” -}} данными" ... ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Не совсем обычный текст сдополнительнымиданными // вывод выше не опечатка — были убраны пробелы перед и после слова “дополнительными”

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

В отличие от текста, который нужно указывать в кавычках, числа автоматически подставляются в вывод:

    
... some_template := "Возможно этот код писало {{ 5 }} человек. Но это не точно..." ... ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Возможно этот код писало 5 человек. Но это не точно...

Как и с текстом, с числами тоже можно использовать маркеры обрезки:

    
... some_template := "Возможно этот код писало {{- 5 }} человек. Но это не точно..." ... ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Возможно этот код писало5 человек. Но это не точно...

Шаблонные переменные

Golang позволяет определять особые переменные, доступные только внутри шаблона.

Как и в самом Go, переменная сначала определяется с указанием имени и значения, а уже потом используется. Определение внутренней переменной происходит с помощью знака доллара:

    
package main import (     "os"     "text/template" ) func main() {     some_template := "Сначала определяем переменную {{- $some_variable :=`Привет, я переменная` }}, а потом используем ее: \"{{ $some_variable }}\""     ready_template, err := template.New("test").Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, ”без данных”) // ВЫВОД: Сначала определяем переменную, а потом используем ее: "Привет, я переменная" }

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

Условные выражения

В шаблонах Go есть возможность ветвления на основе какой либо логики за счет классических для всех языков программирования условных операторов if / else:

    
package main import (     "os"     "text/template" ) func main() {     some_template := "{{ if eq . `hello` -}} Привет! {{ else -}} Пока! {{ end }}" // после каждого условия используем маркер обрезки, чтобы убрать пробел вначале вывода     ready_template, err := template.New("test").Parse(some_template)   if err != nil { panic(err) }     ready_template.Execute(os.Stdout, "hello") // ВЫВОД: Привет! }

В этом примере используется функция eq (equal), которая сравнивает переданное в шаблон значение (к которому мы обращаемся через точку) со строкой hello.

Также обратите внимание, что каждое ветвление завершается оператором end.

На самом деле можно опустить сравнение строк, а сразу передавать булеву переменную, делая код лаконичнее:

    
package main import (     "os"     "text/template" ) func main() {     some_template := "{{ if . -}} Привет! {{ else -}} Пока! {{ end }}"     ready_template, err := template.New("test").Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, false) // ВЫВОД: Пока! }

Циклы

Как правило, шаблоны используются для вывода множества однотипных данных, количество которых меняются от вывода к выводу. Именно для этого и нужны циклы:

    
package main import (     "os"     "text/template" ) func main() {     some_list := []string{ "First", "Second", "Third" }      some_template := "Посчитаем по порядку: {{ range .}}{{.}}, {{ end }}"     ready_template, err := template.New("test").Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, some_list) // ВЫВОД: Посчитаем по порядку:  First, Second, Third, }

В этом примере есть одна проблема — последний элемент списка выводит после себя лишнюю запятую с пробелом. Поэтому код выше можно модернизировать, добавив соответствующую проверку — если элемент последний в списке, то запятая с пробелом не ставится:

    
package main import (     "os"     "text/template" ) func main() {     some_list := []string{ "First", "Second", "Third" }      some_template := "Посчитаем по порядку: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$element}}{{ end }}"     ready_template, err := template.New("test").Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, some_list) // ВЫВОД: Посчитаем по порядку: First, Second, Third }

В этом примере добавляются две новые переменные, которые обновляются каждую итерацию цикла — индекс и значение элемента.

Перед каждым элементом мы выводим запятую с пробелом, но только в том случае, когда индекс ($index) не равен нулю.

Шаблонные функции

Внутри шаблонов можно вызывать свои собственные «кастомные» функции, выполняющие различные операции над переданными аргументами.

Однако, перед непосредственным использованием их нужно предварительно объявить и задекларировать в шаблоне:

    
package main import (     "os"     "text/template" ) func manipulate(first_arg, second_arg int) int {      return first_arg + second_arg }  func main() {     some_list := []int{1, 2, 3 }      some_template := "Сложения индекса и элемента по порядку: {{ range $index, $element := .}}{{ if $index }}, {{ end }}{{$index}} + {{$element}} = {{ do_manipulation $index $element }}{{ end }}"     ready_template, err := template.New("test").Funcs(template.FuncMap{"do_manipulation": manipulate}).Parse(some_template)     if err != nil { panic(err) }     ready_template.Execute(os.Stdout, some_list) // ВЫВОД: Сложения индекса и элемента по порядку: 0 + 1 = 1, 1 + 2 = 3, 2 + 3 = 5 }

В этом примере мы нарочно переименовали Go-функцию manipulate внутри шаблона в do_manipulation — если функционал языка позволяет, то почему бы это не сделать?

В любом случае, могли бы использовать и оригинальное название функции, просто продублировав его:

    
ready_template, err := template.New("test").Funcs(template.FuncMap{"manipulate": manipulate}).Parse(some_template)

Работа с HTML-шаблонами в Go

Как уже было сказано ранее, в Go есть дополнительный пакет для работы с шаблонами на HTML — html/template.

Использование этого пакета, в отличие от стандартного text/template, защищает приложения от атак межсайтового скриптинга (XSS), поскольку Go предотвращает ввод каких-либо данных во время рендеринга.

Соответственно, импорт пакетов немного видоизменяется:

    
import (     "html/template"   "net/http" )

Пакет net/http потребуется для запуска HTTP-сервера на локальной машине, который необходим для теста дальнейшего примера.

Файл шаблона с HTML-разметкой

Лучшей практикой было бы хранение шаблона в отдельном файле. В нашем случае мы создадим файл с оригинальным расширением .html, хотя в своих проектах вы можете использовать абсолютно любое расширение — Golang не накладывает никаких ограничений.

Кстати, файл назовем классическим именем — index.html:

    
<!DOCTYPE html> <html lang="en"> <head>   <meta charset="UTF-8">   <title>Document</title> </head> <body>   <h1>{{ .Tittle }}</h1>   <p> {{ .Text }} </p> </body> </html>

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

Теперь напишем минимальный Golang-код, запускающий HTTP-сервер и отправляющий результат выполнения шаблона в качестве ответа на любые запросы к серверу:

    
package main import (     "os"     "html/template"     "net/http" ) // объявляем структуру, которая будет хранить информацию для генерации шаблона type Content struct {     Title string     Text string } // функция, которая будет обрабатывать HTTP-запросы к серверу func generateResponse(writer http.ResponseWriter, request *http.Request) {     if request.Method == "GET" {         some_template, _ = template.ParseFiles("index.html")         some_content := Content {             Title: "Это заголовок",             Text: "Это текст", }     err := some_template.Execute(writer, some_content) // выполняем шаблон, записывая его вывод в переменную ответа сервера     if err != nil {         panic(err)     } } func main() {     // запускаем HTTP-сервер, указывая ранее созданную функцию обработки запросов в качестве аргумента     http.HandleFunc("/", generateResponse)     err := http.ListenAndServe("localhost:8080", nil)     if err != nil {         log.Fatalln("Случилась какая-то ошибка:", err)     } }

Подготовили для вас выгодные тарифы на облачные серверы

Premium 3.3 ГГц
Санкт-Петербург
12 мес Скидка 10%
Cloud-40

900 ₽/мес

Процессор
2 x 3.3 ГГц
Память
2 ГБ
NVMe
40 ГБ
Канал
1 Гбит/с
Публичный IP
Выбор клиентов
Cloud-50

1 080 ₽/мес

Процессор
2 x 3.3 ГГц
Память
4 ГБ
NVMe
50 ГБ
Канал
1 Гбит/с
Публичный IP

Заключение

Итак. Язык программирования Go предлагает встроенную поддержку для создания динамического контента или для отображения настраиваемого вывода — шаблоны.

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

Сама реализация предполагает несколько вариантов использования:

  • text/template
  • html/template

Помните, что любой шаблон проходит 3 стадии формирования, каждая из которых обеспечивается соответствующей функцией:

  • New. Создание шаблона
  • Parse. Анализ (парсинг) шаблона.
  • Execute. Выполнение шаблона. При этом эта стадия может иметь неограниченное число повторений.

Кстати, подробную информацию о доступных функциях и способах использования можно найти в официальной документации Golang — на страницах text/template и html/template соответственно.

09 ноября 2023 г., обновлено 07 октября 2026
5602
13 минут чтения
Средний рейтинг статьи: 3
Предыдущая статья
Следующая статья

Читайте также

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
Пока нет комментариев