Работа со строками в Go: пошаговая инструкция
Строки в Go — не массив символов, а неизменяемая последовательность байтов. Разберём, как их создавать, склеивать, разбирать на руны и не попасть в ловушку с юникодом — от первой переменной до уверенной работы с кириллицей.

Что нужно до начала
Все примеры проверены на Go 1.22. Подойдёт любая версия от 1.21 и выше — синтаксис строк стабилен с первых релизов, а функции из пакетов strings и unicode/utf8, которые мы используем, появились ещё в Go 1.0.
- Установленный Go — проверьте командой
go versionв терминале. - Любой текстовый редактор. VS Code с расширением Go или GoLand — удобнее всего, но подойдёт и
nano. - Рабочая директория с инициализированным модулем:
go mod init strings-demo.
На всё уйдёт около часа. В конце вы будете уверенно объявлять строки, склеивать их без лишних аллокаций, итерироваться по рунам и безопасно работать с кириллицей.
Шаг 1. Объявление и базовые свойства
Строка в Go — неизменяемая (immutable) последовательность байтов. Это значит, что нельзя заменить третий символ на месте: нужно создать новую строку. Под капотом string — структура из указателя на массив байтов и длины.
package main
import "fmt"
func main() {
// Обычная строка в двойных кавычках
greeting := "Привет, мир!"
// Сырая строка — обратные кавычки, переносы сохраняются как есть
raw := `Строка
без экранирования`
fmt.Println(greeting) // Привет, мир!
fmt.Println(len(greeting)) // 21 — количество байтов, не символов
fmt.Println(raw) // Строка\nбез экранирования (буквально)
}
Проверка: запустите go run main.go. В терминале появятся три строки. Обратите внимание: len вернул 21, хотя видимых символов 12. Каждая кириллическая буква занимает 2 байта в UTF-8 — это главный источник ошибок у новичков.
Шаг 2. Байты, руны и правильный подсчёт символов
Go различает байт (byte, он же uint8) и руну (rune, он же int32). Руна — одна кодовая точка Unicode. Чтобы узнать количество символов, а не байтов, используйте пакет unicode/utf8.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Привет"
fmt.Println("Байтов:", len(s)) // 12
fmt.Println("Рун:", utf8.RuneCountInString(s)) // 6
}
Проверка: go run main.go выводит 12 и 6. Если числа совпали — вы работаете с ASCII-строкой; если нет — в строке многобайтовые символы.
Сноска: руна (rune) — тип int32, представляющий одну кодовую точку Unicode. Термин введён в Go, чтобы разработчик не путал «символ на экране» с «байтом в памяти».
Шаг 3. Итерация: for range vs индекс
Два способа пройти по строке дают разный результат.
| Способ | Единица итерации | Безопасен для UTF-8 |
|---|---|---|
for i := 0; i < len(s); i++ | байт (byte) | Нет — разрежет многобайтовый символ |
for i, r := range s | руна (rune) | Да — i — байтовое смещение, r — целая руна |
package main
import "fmt"
func main() {
s := "Goфер"
// Итерация по рунам
for i, r := range s {
fmt.Printf("байт %d → руна %c (U+%04X)\n", i, r, r)
}
// байт 0 → руна G (U+0047)
// байт 1 → руна o (U+006F)
// байт 2 → руна ф (U+0444)
// байт 4 → руна е (U+0435)
// байт 6 → руна р (U+0440)
}
Проверка: байтовое смещение прыгает с 2 на 4 — это значит, что руна «ф» заняла 2 байта. Если смещения идут подряд — строка полностью ASCII.
Шаг 4. Конкатенация: от наивной до быстрой
Оператор + создаёт новую строку при каждом вызове. Для двух-трёх склеек это нормально. Но в цикле на тысячу итераций вы получите тысячу аллокаций. Правильный инструмент — strings.Builder.
package main
import (
"fmt"
"strings"
)
func main() {
words := []string{"Go", " — ", "это", " ", "просто"}
// Способ 1: strings.Join — когда слайс уже готов
result1 := strings.Join(words, "")
// Способ 2: strings.Builder — когда строка собирается по частям
var b strings.Builder
for _, w := range words {
b.WriteString(w)
}
result2 := b.String()
fmt.Println(result1) // Go — это просто
fmt.Println(result2) // Go — это просто
}
Проверка: обе строки идентичны. Builder под капотом растит один буфер; если знаете примерный размер — вызовите b.Grow(n) до цикла, чтобы избежать лишних переаллокаций.
Строка в Go неизменяема, аstrings.Builder— нет. Именно поэтому Builder быстрее оператора+в циклах: он не копирует весь результат на каждой итерации, а дописывает байты в растущий буфер.
Шаг 5. Поиск, замена, разбиение — пакет strings
Пакет strings покрывает 90 % повседневных задач. Вот самые ходовые функции:
package main
import (
"fmt"
"strings"
)
func main() {
s := " Hello, Gopher! Hello again. "
fmt.Println(strings.TrimSpace(s)) // убираем пробелы по краям
fmt.Println(strings.Contains(s, "Gopher")) // true
fmt.Println(strings.ReplaceAll(s, "Hello", "Hi")) // замена всех вхождений
// Разбиение по разделителю
csv := "один,два,три"
parts := strings.Split(csv, ",")
fmt.Println(parts) // [один два три]
// Проверка префикса и суффикса
fmt.Println(strings.HasPrefix(csv, "один")) // true
fmt.Println(strings.HasSuffix(csv, "три")) // true
}
Проверка: пять строк вывода, каждая соответствует комментарию. Если Contains вернул false — проверьте регистр: функция чувствительна к нему. Для регистронезависимого поиска приводите обе строки к нижнему регистру через strings.ToLower.
Шаг 6. Конвертация между строкой и слайсом байтов/рун
Иногда строку нужно изменить — например, заменить символ по индексу. Поскольку string неизменяем, путь один: преобразовать в []rune, изменить, преобразовать обратно.
package main
import "fmt"
func main() {
s := "Голанг"
// Строка → слайс рун → модификация → строка
runeSlice := []rune(s)
runeSlice[0] = 'г' // заменяем заглавную на строчную
modified := string(runeSlice)
fmt.Println(modified) // голанг
// Строка → слайс байтов (для I/O, хеширования и т.д.)
bytes := []byte(s)
fmt.Println(bytes[0], bytes[1]) // два байта первой руны «Г»
}
Проверка: первый Println выводит «голанг» со строчной буквы. Каждая конвертация string ↔ []byte и string ↔ []rune создаёт копию данных — учитывайте это в горячих путях.
Шаг 7. Форматирование через fmt.Sprintf
package main
import "fmt"
func main() {
name := "мир"
count := 42
// Sprintf возвращает строку, а не печатает в stdout
msg := fmt.Sprintf("Привет, %s! Ответ: %d.", name, count)
fmt.Println(msg) // Привет, мир! Ответ: 42.
}
Проверка: вывод — одна строка с подставленными значениями. Для сложного шаблонирования (HTML, письма) используйте пакеты text/template и html/template — это отдельная тема.
Если не получилось
invalid UTF-8 encoding— строка содержит битые байты. Проверьте источник данных. Функцияutf8.Valid([]byte(s))вернётfalseдля невалидного UTF-8.index out of rangeпри обращенииs[i]— индекс считается в байтах, а вы думали в символах. Переведите строку в[]rune, если нужен доступ по номеру символа.- Вывод «кракозябр» в терминале Windows — терминал может не поддерживать UTF-8. В PowerShell выполните
chcp 65001перед запуском программы. cannot assign to s[0]— строки неизменяемы. Конвертируйте в[]runeили[]byte, измените, верните обратно вstring.- Конкатенация в цикле работает медленно — используйте
strings.Builder. Если профилирование показывает аллокации — добавьтеb.Grow(expectedSize).
Что дальше
Строки — входная точка почти в любую задачу: парсинг конфигов, HTTP-хендлеры, логирование. Следующие шаги:
- Пакет
strconv— конвертация строк в числа и обратно. Без него не обойтись при чтении пользовательского ввода. - Регулярные выражения — пакет
regexp. В Go используется движок RE2, рассчитанный на линейное время работы; обратных ссылок нет. - Пакет
bufio.Scanner— построчное чтение больших файлов без загрузки всего в память. - Бенчмарки — напишите
_test.goсb.Runи сравните+,JoinиBuilderна своих объёмах данных. Реальные цифры важнее чужих рекомендаций.
Проектная работа: От проблемы до защиты: продукт, этапы, вопросы комиссии
Спросить за 15 ₽Источники: Effective Go — Strings (официальная документация Go), Пакет strings — стандартная библиотека Go
Частые вопросы
Почему len() возвращает больше символов, чем я вижу?
Можно ли изменить один символ в строке без копирования?
strings.Builder или bytes.Buffer — что выбрать?
Примеры работают на Go 1.18 или только на 1.22?
Как сравнивать строки без учёта регистра?
Что делать, если строка содержит невалидный UTF-8?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.