Форматирование вывода в Go: что нужно знать до начала
Пакет fmt в Go покрывает 90% задач по форматированию строк, но его глаголы, флаги и интерфейсы Stringer/Formatter работают не так, как printf в C. Разбираемся, что нужно усвоить до написания первого сервиса.

Что понадобится перед стартом
Go установлен и работает. Все примеры проверены на Go 1.22. Если у вас 1.21 или ниже — синтаксис fmt не менялся с версии 1.0, разница только в новых функциях стандартной библиотеки, которые здесь не затрагиваются.
- Установленный Go 1.18+ (рекомендуется 1.22).
- Любой текстовый редактор или IDE с поддержкой Go (VS Code + расширение Go, GoLand).
- Терминал, в котором можно запустить
go run main.go.
Результат прохождения материала — вы будете уверенно выбирать нужный глагол форматирования, управлять шириной и точностью, форматировать собственные типы и понимать, куда смотреть при отладке неожиданного вывода.
Три семейства функций: Print, Fprint, Sprint
Пакет fmt делит функции на три группы по тому, куда идёт результат. Это не очевидно, если привыкли к одному printf.
| Семейство | Куда пишет | Возвращает строку? | Типичное применение |
|---|---|---|---|
| Print / Printf / Println | os.Stdout | Нет | Отладочный вывод, CLI-утилиты |
| Fprint / Fprintf / Fprintln | Любой io.Writer | Нет | Запись в файл, HTTP-ответ, буфер |
| Sprint / Sprintf / Sprintln | Никуда | Да | Сборка строк для логов, сообщений, шаблонов |
Частая ошибка новичка — собирать строку через fmt.Sprintf, а потом передавать её в fmt.Fprintf(w, s). Если строка содержит символ %, форматтер попытается его интерпретировать. Безопасный вариант: fmt.Fprint(w, s) без шаблона или fmt.Fprintf(w, "%s", s).
Глаголы форматирования: не только %s и %d
Глагол (verb) — символ после %, который задаёт способ отображения значения. Их больше двадцати, но в повседневной работе достаточно знать десять.
Самые частые глаголы
| Глагол | Принимает | Что делает | Пример вывода |
|---|---|---|---|
| %v | Любой тип | Значение в формате по умолчанию | {Alice 30} |
| %+v | Структуры | Значение + имена полей | {Name:Alice Age:30} |
| %#v | Любой тип | Go-синтаксис значения | main.User{Name:"Alice", Age:30} |
| %T | Любой тип | Тип значения | main.User |
| %s | string, []byte | Строка как есть | hello |
| %q | string, rune | Строка в кавычках с экранированием | "hello\nworld" |
| %d | Целые числа | Десятичное число | 42 |
| %x | Целые, []byte | Шестнадцатеричное, строчные | 2a |
| %f | float32/64 | Десятичная дробь | 3.141593 |
| %e | float32/64 | Научная нотация | 3.141593e+00 |
Глагол %v — универсальный дебаг-инструмент. Но именно из-за универсальности он даёт минимум контроля: ни ширину числа, ни точность дроби, ни кавычки у строки. Для пользовательского вывода почти всегда лучше выбрать конкретный глагол.
Ширина и точность
Между % и глаголом можно указать ширину и точность через точку: %8.2f — минимум 8 символов общей ширины, 2 знака после запятой.
package main
import "fmt"
func main() {
price := 49.9
// Ширина 10, точность 2, выравнивание по правому краю (по умолчанию)
fmt.Printf("|%10.2f|\n", price) // | 49.90|
// Флаг «-» — выравнивание по левому краю
fmt.Printf("|%-10.2f|\n", price) // |49.90 |
// Флаг «0» — заполнение нулями
fmt.Printf("|%010.2f|\n", price) // |0000049.90|
}
Проверка: скопируйте код в файл main.go, выполните go run main.go. Три строки с числом 49.90, выровненным по-разному — значит, всё работает.
Для строк точность ограничивает длину: %.5s от строки "привет" выведет первые 5 байт, а не 5 рун. Кириллическая буква занимает 2 байта в UTF-8, поэтому результат может оборваться посреди символа. Если нужна обрезка по рунам, форматируйте вручную через []rune.
Интерфейс Stringer: контролируем %v и %s
Когда fmt встречает %v или %s, он проверяет, реализует ли тип интерфейс fmt.Stringer — единственный метод String() string. Если да, вызывается он, а не стандартное представление полей.
package main
import "fmt"
type IPAddr [4]byte
// String реализует fmt.Stringer
func (ip IPAddr) String() string {
return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3])
}
func main() {
addr := IPAddr{192, 168, 0, 1}
fmt.Println(addr) // 192.168.0.1
fmt.Printf("Адрес: %v\n", addr) // Адрес: 192.168.0.1
fmt.Printf("Тип: %T\n", addr) // Тип: main.IPAddr
}
Проверка: вывод содержит 192.168.0.1, а не [192 168 0 1].
Ловушка: если String() внутри себя вызывает fmt.Sprintf("%s", ip) с тем же типом — получите бесконечную рекурсию и panic. Используйте приведение к базовому типу или конкретные поля.
Сноска. Stringer — интерфейс из пакета fmt, аналог toString() в Java и __str__ в Python. Реализуется неявно: достаточно объявить метод с нужной сигнатурой, никакого ключевого слова implements не требуется.
Интерфейс error и %w: форматирование ошибок
Интерфейс error — это тоже, по сути, «форматирование»: метод Error() string. Но с Go 1.13 появился глагол %w в fmt.Errorf, который оборачивает ошибку с сохранением цепочки.
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) error {
_, err := os.Open(path)
if err != nil {
// %w оборачивает err — цепочку потом можно развернуть через errors.Is / errors.As
return fmt.Errorf("не удалось прочитать конфиг %s: %w", path, err)
}
return nil
}
func main() {
err := readConfig("/nonexistent.yaml")
if err != nil {
fmt.Println(err)
// Проверяем, что внутри — *os.PathError
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Путь из ошибки:", pathErr.Path)
}
}
}
Если вместо %w написать %v, текст ошибки будет таким же, но errors.Is и errors.As перестанут находить оригинальную ошибку — цепочка разорвётся. Это частый источник багов в обработке ошибок.
Если не получилось
- Вместо значения видите
%!d(string=hello)— глагол не совпадает с типом.%dожидает целое число, а получил строку. Исправьте глагол или приведите тип. - Лишние пробелы или переносы —
Printlnдобавляет пробелы между аргументами и\nв конце.Printfне добавляет ничего. Путаница между ними — самая частая причина «поехавшего» вывода. - Паника при
String()— рекурсия. ВнутриString()не передавайте сам объект вSprintfс глаголами%sили%v. - Обрезанная кириллица при
%.Ns— точность считает байты, а не руны. Для UTF-8 строк обрезайте через[]rune(s)[:N]. %wне работает — глагол%wдоступен только вfmt.Errorf. Вfmt.Sprintfон не оборачивает ошибку и выведет предупреждение vet-а.
Что делать дальше
Форматирование — вход в две большие темы: логирование и сериализация. Следующий шаг зависит от задачи:
- Если пишете CLI-утилиту — посмотрите пакет
text/tabwriterиз стандартной библиотеки: он выравнивает табличный вывод в терминале без внешних зависимостей. - Если пишете сервис — переходите к структурному логированию. Пакет
log/slog(стандартная библиотека с Go 1.21) позволяет выводить логи в JSON, и знаниеfmt-глаголов помогает при кастомных форматерах. - Для шаблонизации HTML или текста — пакеты
html/templateиtext/templateиспользуют свой синтаксис{{ }}, но внутри часто вызывают тот жеStringer.
Держите под рукой раздел документации по fmt — он компактный и описывает все глаголы, флаги и интерфейсы на одной странице.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: Документация пакета fmt — Go standard library, Документация пакета errors — Go standard library
Частые вопросы
Чем %v отличается от %s?
Можно ли использовать fmt.Sprintf для сборки SQL-запросов?
Почему fmt.Println добавляет пробелы между аргументами, а Printf — нет?
У меня Go 1.18 — примеры из статьи будут работать?
Что если я реализую и Stringer, и fmt.Formatter на одном типе?
Как понять, что я передал лишний или недостающий аргумент в Printf?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.