Введение: Зачем форматировать строку, если не нужно печатать?
Вы когда-нибудь сталкивались с ситуацией, когда нужно красиво собрать строку из нескольких частей, вставить переменные, добавить отступы, но при этом не выводить результат на экран? Кажется, что все примеры в учебниках по Go заканчиваются вызовом fmt.Println, а что делать, если строка нужна для отправки по сети, записи в файл или передачи в другую функцию?
Этот вопрос мучает тысячи разработчиков, и не зря — на Stack Overflow он собрал десятки тысяч просмотров. В этой статье мы разберем как форматировать строки в Go без печати, используя реальные примеры кода. Вы узнаете о мощном инструменте fmt.Sprintf, о современных подходах с strings.Builder и о низкоуровневом bytes.Buffer. Поехали!
Проблема: Printf печатает, а мне нужно сохранить
В Go есть замечательная функция fmt.Printf, которая форматирует строку и сразу выводит её в стандартный поток вывода. Но что если вы хотите получить отформатированную строку как значение, а не как побочный эффект? Например:
name := "Алиса"
age := 30
// Хочу получить строку "Меня зовут Алиса, мне 30 лет"
// но не печатать её сразу
Начинающие разработчики часто пишут так:
fmt.Printf("Меня зовут %s, мне %d лет", name, age)
// Строка напечатана, но мы её потеряли!
Это работает, но результат улетает в консоль. А нам нужно сохранить его в переменную для дальнейшей обработки. Решение есть, и оно элегантное.
Решение №1: fmt.Sprintf — король форматирования
В пакете fmt есть функция-близнец Printf, которая называется Sprintf. Буква "S" в начале означает "String" — она возвращает строку, а не печатает её. Это самый прямой и понятный способ форматирования строк в Go без печати.
Базовый пример с Sprintf
package main
import (
"fmt"
)
func main() {
name := "Боб"
age := 25
score := 98.5
// Форматируем без печати
message := fmt.Sprintf("Привет, %s! Тебе %d лет. Твой результат: %.1f баллов.", name, age, score)
// Теперь message содержит отформатированную строку
fmt.Println(message) // Печатаем только сейчас
}
Вывод:
Привет, Боб! Тебе 25 лет. Твой результат: 98.5 баллов.
Обратите внимание: Sprintf использует те же самые плейсхолдеры ( %s, %d, %f ), что и Printf. Разница только в том, что результат возвращается, а не выводится.
Продвинутые плейсхолдеры
Вы можете использовать все возможности форматирования Go:
package main
import "fmt"
func main() {
// Выравнивание по ширине
leftAligned := fmt.Sprintf("%-10s %d", "Товар", 10)
rightAligned := fmt.Sprintf("%10s %d", "Товар", 10)
// Форматирование чисел
price := 1234.5678
formattedPrice := fmt.Sprintf("Цена: $%.2f (с НДС: $%.2f)", price, price*1.2)
// Шестнадцатеричный вывод
hexValue := fmt.Sprintf("Hex: %x, Dec: %d", 255, 255)
fmt.Println(leftAligned)
fmt.Println(rightAligned)
fmt.Println(formattedPrice)
fmt.Println(hexValue)
}
Вывод:
Товар 10
Товар 10
Цена: $1234.57 (с НДС: $1481.48)
Hex: ff, Dec: 255
fmt.Sprintf — это ваш универсальный инструмент для форматирования строк Go. Он работает быстро, понятен и покрывает 90% задач.
Решение №2: strings.Builder — для сложных конструкций
Что если вам нужно собрать строку из множества частей, добавляя их по одной? Например, формировать HTML, JSON или SQL-запрос. Использовать Sprintf для каждой части неэффективно — вы будете создавать много промежуточных строк, нагружая сборщик мусора. На помощь приходит strings.Builder.
Как работает strings.Builder
strings.Builder — это структура, которая накапливает строки внутри себя, минимизируя выделение памяти. После сборки вы получаете готовую строку одним вызовом String().
package main
import (
"fmt"
"strings"
)
func main() {
var sb strings.Builder
// Добавляем части строки
sb.WriteString("Пользователь: ")
sb.WriteString("Иван")
sb.WriteString(", возраст: ")
sb.WriteString(fmt.Sprintf("%d", 30))
sb.WriteString(", статус: ")
sb.WriteString("активен")
// Получаем итоговую строку
result := sb.String()
fmt.Println(result)
}
Вывод:
Пользователь: Иван, возраст: 30, статус: активен
Почему это лучше, чем конкатенация?
Наивная конкатенация через + создаёт новую строку при каждом сложении. Если у вас 100 частей, будет создано 100 промежуточных строк. strings.Builder выделяет память один раз и расширяет буфер по мере необходимости. Для больших объёмов данных разница в производительности может быть колоссальной.
Продвинутый пример: генерация CSV
package main
import (
"fmt"
"strings"
)
func main() {
var sb strings.Builder
// Заголовок CSV
sb.WriteString("Имя,Возраст,Город\n")
// Данные
data := []struct {
name string
age int
city string
}{
{"Анна", 28, "Москва"},
{"Петр", 35, "СПб"},
{"Елена", 22, "Казань"},
}
for _, row := range data {
sb.WriteString(fmt.Sprintf("%s,%d,%s\n", row.name, row.age, row.city))
}
csvData := sb.String()
fmt.Println(csvData)
}
Вывод:
Имя,Возраст,Город
Анна,28,Москва
Петр,35,СПб
Елена,22,Казань
strings.Builder идеален, когда вы форматируете строки в Go в цикле или собираете их из множества фрагментов.
Решение №3: bytes.Buffer — низкоуровневая мощь
До появления strings.Builder (Go 1.10) разработчики использовали bytes.Buffer. Этот инструмент всё ещё актуален, особенно если вы работаете с байтами, а не только со строками. bytes.Buffer реализует интерфей