Содержание

Строки, руны и регулярные выражения в Go

Строки выглядят простыми, пока в них только английские буквы и цифры. Но реальные программы работают с именами, адресами, кириллицей, эмодзи, логами, JSON, HTML, командами терминала и данными от пользователей. В этот момент важно понимать, что строка - это не просто "набор букв".

В Go строка хранит байты. Обычно эти байты являются текстом в кодировке UTF-8. Это хорошая современная кодировка, но она означает, что один видимый символ может занимать больше одного байта.

Если это понять с самого начала, многие ошибки станут очевидными: почему len("Привет") возвращает не 6, почему нельзя обрезать пользовательский текст через text[:10], почему range по строке ведет себя не так, как обычный индексный цикл.

string - это последовательность байтов

Тип string в Go неизменяемый. После создания строки нельзя заменить символ внутри нее. Можно только создать новую строку.

text := "Привет" fmt.Println(len(text)) // количество байтов, не букв

Для слова Привет результат будет больше 6, потому что кириллические символы в UTF-8 занимают по два байта.

Если пройти по строке через индекс, вы получите байты:

for i := 0; i < len(text); i++ { fmt.Println(i, text[i]) }

Это полезно, когда вы работаете с протоколами, файлами или бинарными данными. Но для текста, который видит человек, чаще нужен другой подход.

rune и Unicode

rune - это псевдоним для int32. Обычно он представляет Unicode-кодпоинт, то есть один элемент текста.

Для посимвольного обхода используйте range:

for index, r := range text { fmt.Println(index, r, string(r)) }

index здесь все равно байтовый индекс, а r - руна. Это важно: если вам нужен порядковый номер символа, заведите отдельный счетчик.

position := 0 for _, r := range text { fmt.Println(position, string(r)) position++ }

[]byte и []rune

Иногда строку нужно преобразовать:

bytes := []byte(text) runes := []rune(text)

[]byte нужен для файлов, сети, хеширования и низкоуровневой обработки. []rune удобен, когда нужно работать именно с символами:

runes := []rune("Привет") firstThree := string(runes[:3]) fmt.Println(firstThree) // При

Но не превращайте каждую строку в []rune автоматически. Это выделяет память. Используйте такой подход, когда действительно нужна индексация по символам.

Пакет strings

Пакет strings закрывает большинство повседневных задач:

strings.TrimSpace(input) strings.ToLower(name) strings.HasPrefix(command, "/") strings.HasSuffix(filename, ".log") strings.Contains(text, "error") strings.Split(line, ",") strings.Join(parts, ";") strings.ReplaceAll(text, " ", "_")

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

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

func normalizeCommand(input string) string { input = strings.TrimSpace(input) input = strings.ToLower(input) return input }

Теперь " HELP " и "help" будут обработаны одинаково.

strings.Builder

Строки неизменяемы. Когда вы делаете result += part в цикле, Go каждый раз создает новую строку. Для маленького кода это не страшно, но для больших циклов лучше использовать strings.Builder.

var builder strings.Builder for _, item := range items { builder.WriteString(item) builder.WriteByte('\n') } result := builder.String()

Мысленно Builder похож на коробку, в которую вы постепенно складываете куски текста. В конце вы закрываете коробку и получаете одну готовую строку.

strconv

Пакет strconv преобразует строки в числа, bool и обратно.

age, err := strconv.Atoi("42") if err != nil { return err } port, err := strconv.ParseInt("8080", 10, 64) price, err := strconv.ParseFloat("12.50", 64) enabled, err := strconv.ParseBool("true")

Обратное преобразование:

text := strconv.Itoa(42)

Всегда обрабатывайте ошибку парсинга. Если пользователь ввел "ten" вместо "10", программа должна дать понятный ответ, а не продолжить с нулевым значением.

fmt.Sprintf

fmt.Sprintf собирает строку по шаблону:

message := fmt.Sprintf("Файл %s: %d ошибок", filename, count)

Частые плейсхолдеры:

ПлейсхолдерЗначение
%sстрока
%dцелое число
%fчисло с плавающей точкой
%.2fчисло с двумя знаками после точки
%vзначение в стандартном виде
%+vструктура с именами полей
%#vGo-представление значения
%qстрока в кавычках

Для сообщений пользователю используйте понятные форматы. Для отладки структур часто полезны %+v и %#v.

Регулярные выражения

Регулярное выражение - это шаблон для поиска текста. Оно удобно для логов, валидации, извлечения частей строки и проверки формата.

pattern := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2})\s+(ERROR|INFO)\s+(.+)$`) match := pattern.FindStringSubmatch(line) if match == nil { return nil } date := match[1] level := match[2] message := match[3]

match[0] содержит всю найденную строку. match[1], match[2] и дальше содержат группы в круглых скобках.

MustCompile подходит для шаблона, который известен на этапе написания кода. Если шаблон приходит от пользователя, используйте regexp.Compile:

pattern, err := regexp.Compile(inputPattern) if err != nil { return fmt.Errorf("invalid pattern: %w", err) }

Когда регулярки не нужны

Регулярные выражения мощные, но не всегда лучший инструмент.

Если нужно проверить префикс, используйте:

strings.HasPrefix(value, "user_")

Если нужно разделить CSV-подобную строку, сначала подумайте о strings.Split или специализированном парсере.

Если нужно проверить email в реальном продукте, простая регулярка часто будет либо слишком слабой, либо слишком сложной. Иногда лучше проверить базовую форму и отправить письмо подтверждения.

text/template и html/template

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

tmpl := template.Must(template.New("message").Parse( "Привет, {{.Name}}! У тебя {{.Count}} уведомлений.", )) data := struct { Name string Count int }{ Name: "Alice", Count: 3, } if err := tmpl.Execute(os.Stdout, data); err != nil { return err }

Для HTML используйте html/template: он экранирует опасные символы и помогает не сделать XSS.

Практический пример: парсинг строки лога

func parseLogLine(line string) (string, string, string, bool) { pattern := regexp.MustCompile(`^(\d{4}-\d{2}-\d{2})\s+(\w+)\s+(.+)$`) match := pattern.FindStringSubmatch(strings.TrimSpace(line)) if match == nil { return "", "", "", false } return match[1], match[2], match[3], true }

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

Мини-чеклист

  1. Для байтов используйте []byte.
  2. Для символов используйте rune и range.
  3. Для простого поиска используйте strings, не регулярки.
  4. Для чисел используйте strconv, не ручной парсинг.
  5. Для сборки текста в цикле используйте strings.Builder.
  6. Для регулярки из кода используйте regexp.MustCompile.
  7. Для регулярки от пользователя используйте regexp.Compile.
  8. Для HTML используйте html/template, не text/template.

Следующий шаг после статьи

Закрепите тему во вводном проекте без регистрации, а затем переходите к курсам.

Продолжить изучение

Выбери следующую статью по маршруту или углубись в смежную тему.

Похожие статьи