Байты и руны в Go: byte и rune
На первый взгляд, типы byte и rune могут показаться избыточными, ведь технически это просто синонимы (алиасы) для uint8 и int32:
type byte = uint8
type rune = int32
Однако их существование — это не просто синтаксический сахар. Они несут в себе глубокий семантический смысл и помогают писать более понятный и корректный код, особенно при работе с текстом и двоичными данными.
1. byte — Атом данных
byte — это фундаментальная единица памяти, "кирпичик", из которого состоит любая цифровая информация.
Аналогия: Представьте себе один кирпич. Сам по себе он — просто объект. Из него можно построить стену дома, выложить дорожку или использовать как пресс для бумаг. Его назначение зависит от контекста.
Тип byte говорит программисту: "Я работаю с сырыми, необработанными данными". Это может быть:
- Содержимое файла (изображение, аудио, исполняемый файл).
- Данные, полученные по сети.
- Двоичное представление чего-либо.
Когда вы видите []byte, вы должны думать о последовательности байтов, а не о тексте. Число 65 в этом контексте — это просто число 65, а не обязательно буква 'A'.
Когда использовать byte:
- Чтение и запись файлов (
os.ReadFile). - Работа с сетевыми протоколами.
- Кодирование/декодирование данных (например, в JSON или бинарные форматы).
// Слайс байтов для хранения бинарных данных
// Например, первые 4 байта PNG файла
var pngHeader []byte = []byte{137, 80, 78, 71}
fmt.Printf("Тип данных: %T\n", pngHeader) // Тип данных: []uint8
Обратите внимание, что fmt показывает базовый тип []uint8, но в коде мы используем []byte для ясности.
2. rune — Концепция символа
rune — это представление одного символа Unicode.
Аналогия: Представьте себе букву 'A' или эмодзи '🚀'. Это не физические объекты, а абстрактные концепции, идеи. У каждой такой идеи есть уникальный номер в стандарте Unicode, называемый code point.
- Символ 'A' имеет код
65. - Символ 'Я' имеет код
1071. - Символ '🚀' имеет код
128640.
Поскольку номеров символов очень много, одного байта (uint8, 0-255) не хватит для их хранения. Поэтому в Go для руны выбрали тип int32, который гарантированно вмещает любой существующий и будущий символ Unicode.
Тип rune говорит программисту: "Я работаю с текстовым символом, а не просто с числом".
var russianChar rune = 'Я'
var emojiChar rune = '🚀'
// За кулисами это просто числа типа int32
fmt.Printf("Символ: %c, Unicode-код: %d, Тип: %T\n", russianChar, russianChar, russianChar)
// Вывод: Символ: Я, Unicode-код: 1071, Тип: int32
fmt.Printf("Символ: %c, Unicode-код: %d, Тип: %T\n", emojiChar, emojiChar, emojiChar)
// Вывод: Символ: 🚀, Unicode-код: 128640, Тип: int32
3. Как rune превращается в byte? Кодировка UTF-8
Итак, у нас есть абстрактный символ (rune) и физические единицы хранения (byte). Чтобы сохранить текст в памяти или файле, руны нужно закодировать — то есть представить в виде последовательности байтов.
Go по умолчанию использует кодировку UTF-8. Её особенность — переменная длина символа:
- Простые символы ASCII (A-Z, 0-9) кодируются одним байтом.
- Символы кириллицы — двумя байтами.
- Многие азиатские символы — тремя байтами.
- Эмодзи — четырьмя байтами.
Это приводит к одному из самых частых источников путаницы для новичков в Go.
Практический пример: len() и подсчет символов
Функция len() применительно к строке возвращает ее длину в байтах, а не в символах (рунах)!
str := "Go-ракета 🚀"
// 1. Считаем БАЙТЫ (неправильно для символов)
// "Go" -> 2 байта
// "-" -> 1 байт
// "ракета" -> 6 символов * 2 байта = 12 байт
// " " -> 1 байт
// "🚀" -> 4 байта
// Итого: 2 + 1 + 12 + 1 + 4 = 20
fmt.Printf("Длина строки в байтах: %d\n", len(str)) // Вывод: Длина строки в байтах: 20
// 2. Считаем РУНЫ (правильный способ подсчета символов)
runes := []rune(str)
fmt.Printf("Длина строки в рунах (символах): %d\n", len(runes)) // Вывод: Длина строки в рунах (символах): 11
// 3. Более эффективный способ просто посчитать, не создавая новый слайс
import "unicode/utf8"
fmt.Printf("Длина через utf8.RuneCountInString: %d\n", utf8.RuneCountInString(str)) // Вывод: 11
Итоговая таблица
| Критерий | byte | rune |
|---|---|---|
| Технический тип | uint8 | int32 |
| Смысловое значение | Сырые данные, один байт информации. | Один символ Unicode, текстовая концепция. |
| Размер | Всегда 1 байт. | Всегда 4 байта. |
| Когда использовать | Работа с файлами, сетью, бинарными протоколами. | Работа с текстом, итерация по символам строки. |
| Пример | []byte{255, 0, 10} | var symbol rune = '世' |