Как правильно итерироваться по строке в Go и в чем ключевые отличия подходов?

Ответ

В Go строка — это неизменяемая последовательность байт в кодировке UTF-8. Из-за этого существуют два принципиально разных способа итерации с разным результатом.

1. Итерация с помощью for range (по рунам)

Этот способ является предпочтительным для работы с символами. Цикл for range автоматически декодирует UTF-8 последовательности в руны (тип rune, который является псевдонимом для int32).

  • Что возвращает: Индекс начала руны в байтовой последовательности и саму руну.
  • Преимущество: Корректно обрабатывает многобайтовые символы (кириллица, эмодзи и т.д.).
s := "你好, Go!"
for i, r := range s {
    // i будет 0, 3, 6, 7, 8, 9, 10
    // r будет '你', '好', ',', ' ', 'G', 'o', '!'
    fmt.Printf("Индекс байта: %d, Руна: %cn", i, r)
}

2. Итерация по индексу (по байтам)

Этот способ работает напрямую с байтами, из которых состоит строка.

  • Что возвращает: Индекс байта и сам байт (тип byte, псевдоним для uint8).
  • Проблема: Для многобайтовых символов этот подход вернет части символа, что приведет к некорректному результату.
s := "Go"
for i := 0; i < len(s); i++ {
    fmt.Printf("Индекс: %d, Байт: %vn", i, s[i])
}
// Для строки "Go" результат будет корректным, т.к. символы однобайтовые.
// Для строки "你好" результат будет неверным.

Ключевые моменты:

  • len(s) возвращает количество байт в строке, а не символов.
  • Для подсчета количества рун (символов) используйте utf8.RuneCountInString(s).