Перейти к основному содержимому

Байты и руны в 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

Итоговая таблица

Критерийbyterune
Технический типuint8int32
Смысловое значениеСырые данные, один байт информации.Один символ Unicode, текстовая концепция.
РазмерВсегда 1 байт.Всегда 4 байта.
Когда использоватьРабота с файлами, сетью, бинарными протоколами.Работа с текстом, итерация по символам строки.
Пример[]byte{255, 0, 10}var symbol rune = '世'