Строки и руны
Почему строка в Go — это байты, что такое руна и как правильно считать символы.
Руна (rune) — это один символ Unicode (кодовая точка), псевдоним для
int32. Строка же хранится как последовательность байтов UTF-8.
Строки неизменяемы и хранят байты
Строка в Go — это неизменяемая последовательность байтов, обычно в кодировке UTF-8. Длина len(s) возвращает число байтов, а не символов. Для латиницы это одно и то же, но кириллица и эмодзи занимают несколько байтов каждый.
package main
import "fmt"
func main() {
s := "Go"
r := "Привет"
fmt.Println(len(s)) // 2 байта
fmt.Println(len(r)) // 12 байт: каждая буква по 2 байта
}Вывод:
2 12
Поэтому len("Привет") — это 12, а не 6. Это типичная ловушка для тех, кто привык к строкам как к массиву символов.
Руны: считаем символы правильно
Чтобы работать с символами, а не байтами, строку перебирают через for range — он автоматически декодирует UTF-8 и выдаёт руны. А для подсчёта символов есть utf8.RuneCountInString.
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Привет"
fmt.Println(utf8.RuneCountInString(s)) // 6 символов
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
}Вывод:
6 0: П 2: р 4: и 6: в 8: е 10: т
Обратите внимание: индексы идут 0, 2, 4… — это байтовые смещения, ведь каждая кириллическая руна занимает по 2 байта. Глагол %c печатает руну как символ.
Преобразования: []byte и []rune
Строку можно превратить в срез байтов или срез рун и обратно. []rune удобен, когда нужен доступ к символу по индексу или разворот строки.
package main
import "fmt"
func main() {
s := "Go"
bytes := []byte(s) // [71 111]
runes := []rune("Дом")
fmt.Println(bytes)
fmt.Println(string(runes[0])) // первый символ
fmt.Println(len(runes)) // 3 символа
}Вывод:
[71 111] Д 3
| Выражение | Смысл |
len(s) | число байтов |
utf8.RuneCountInString(s) | число символов |
[]byte(s) | срез байтов |
[]rune(s) | срез символов (рун) |
Итог
- Строка — неизменяемые байты UTF-8;
lenсчитает байты, не символы. - Руна (
rune) — символ Unicode; перебор черезfor rangeдаёт руны. - Символы считают через
utf8.RuneCountInString, доступ по индексу — через[]rune.