Что такое руны golang

Узнай, почему руны в Golang круче байтов и как они спасают от кракозябр в строках

При изучении языка программирования Go (Golang) многие разработчики, особенно те, кто привык к языкам с другим подходом к работе со строками, сталкиваются с понятием «руна» (rune). В этой статье мы подробно разберем, что такое руны в Go, зачем они нужны, как они связаны со строками и байтами, и почему без них невозможно писать по-настоящему интернациональные приложения.

Определение: Что такое rune?

В самом простом понимании, rune в Golang, это псевдоним (alias) для типа int32.

Технически, объявление руны в стандартной библиотеке Go выглядит так:

type rune = int32

Но зачем нужен еще один тип для 32-битного целого числа? Ответ кроется в работе с текстом. Руна в Go представляет собой одну кодовую точку Unicode. Независимо от того, какой это символ: латинская буква «A», китайский иероглиф «» или эмодзи «🚀», руна способна хранить его полное числовое значение по стандарту Unicode.

Проблема: Строки, байты и Unicode

Чтобы понять ценность рун, нужно понять, как устроены строки в Go. Строка в Golang — это по сути слайс байтов (read-only slice of bytes). Байт в Go (тип byte) — это псевдоним для uint8, то есть он может хранить значения от 0 до 255.

В старые добрые времена, когда компьютеры в основном работали с английским языком, использовалась кодировка ASCII. Один символ = один байт. Все было просто. Но с появлением Unicode стало ясно, что 256 значений одного байта недостаточно для всех символов мира. Для кодирования многих символов (например, кириллицы, иероглифов или эмодзи) требуется 2, 3 или даже 4 байта.

В Go строки по умолчанию кодируются в UTF-8 — переменной кодировке, где каждый символ может занимать разное количество байтов. И вот тут возникает ловушка:

  • Английская ‘A’ занимает 1 байт.
  • Русская ‘Ш’ занимает 2 байта.
  • Китайский » занимает 3 байта.
  • Эмодзи ‘😀’ занимает 4 байта.

Если вы попытаетесь пройтись циклом по строке как по слайсу байтов, вы получите не символы, а отдельные байты, из которых состоят символы. Для английского текста это сработает, но для любого другого вы получите «битые» символы (кракозябры). Именно здесь на сцену выходят руны.

Разница между byte, string и rune

Давайте четко разграничим эти три концепции:

  1. byte (uint8), атомарная единица данных в памяти. Строка состоит из байтов.
  2. rune (int32) — логическая единица текста. Представляет один символ Unicode, независимо от того, сколько байтов он занимает в памяти.
  3. string — неизменяемая последовательность байтов, которая по договоренности в Go чаще всего интерпретируется как текст в кодировке UTF-8.

Как работают руны на практике

Итерация по строке

Если вы используете классический цикл for со счетчиком, вы итерируетесь по байтам. А если используете конструкцию for .;. range, Go автоматически преобразует байты в руны:

При итерации через range, Go под капотом декодирует UTF-8 последовательность и выдает вам руну (целое число int32), представляющую полноценный символ, а также индекс байта, с которого этот символ начался в строке.

Преобразование типов

Вы можете легко конвертировать строку в слайс рун и обратно. Это невероятно полезная операция.

Когда вы делаете преобразование []rune(строка), Go проходит по всей строке, собирает все UTF-ы и складывает их в слайс 32-битных чисел. Теперь вы можете обращаться к символам по индексу так же, как если бы это была обычная строка в Python или PHP, где каждый символ занимает одну ячейку массива.

Длина строки

Это классическая ловушка для новичков. Функция len, примененная к строке, возвращает количество байтов, а не количество символов!

Если строка содержит слово «Привет», len вернет 12 (так как 6 кириллических букв по 2 байта каждая), а не 6. Чтобы узнать реальное количество символов (рун) в строке, нужно сначала преобразовать ее в слайс рун: len([]rune(«Привет»)) вернет 6.

Альтернативный и более оптимизированный способ подсчета символов в UTF-8 строке без выделения памяти под новый слайс — использование функции utf8.RuneCountInString из стандартного пакета unicode/utf8.

Литералы рун

В коде Go вы можете создать руну (числовую кодовую точку Unicode), используя одинарные кавычки. В отличие от строк, которые обрамляются двойными кавычками или обратными апострофами, одинарные кавычки всегда означают руну.

Примеры литералов рун:

  • ‘a’ — руна строчной латинской ‘a’ (числовое значение 97).
  • ‘Я’, руна заглавной русской ‘Я’ (числовое значение 1071).
  • ‘
    ‘ — руна переноса строки (числовое значение 10).
  • ‘♫’ — руна музыкального символа ♫, заданная через Unicode-экранирование.

Когда следует использовать руны?

Работа с рунами необходима в ряде специфических задач:

  • Обработка и валидация текста: Если вам нужно проверить, является ли символ буквой, цифрой или пробелом, вы должны работать с рунами, используя пакет unicode (например, функции unicode.IsLetter или unicode.IsDigit).
  • Трансформация текста: Приведение символов к верхнему или нижнему регистру с помощью unicode.ToUpper и unicode.ToLower работает исключительно с рунами.
  • Реверс строк: Чтобы перевернуть строку задом наперед, ее обязательно нужно преобразовать в []rune. Если перевернуть слайс байтов, многобайтовые символы сломаются, и вы получите бессмысленный набор байтов.
  • Поиск и замена подстрок: Если вы ищете конкретный символ в строке, гораздо корректнее и безопаснее искать руну, а не байт.

Подводные камни: Комбинируемые символы

Хотя руны решают 99% проблем с интернационализацией, они не являются серебряной пулей. В Unicode существуют так называемые комбинируемые диакритические знаки (combining characters).

Например, буква «é» может быть представлена в Unicode двумя способами:

  1. Как одна кодовая точка (одна руна) U+00E9.
  2. Как комбинация двух кодовых точек (двух рун): обычной «e» (U+0065) и диакритического знака « ́» (U+0301).

Визуально пользователь увидит одно и то же. Но с точки зрения Go и типа rune, во втором случае это будет две руны. Это значит, что если вы решите обрезать строку до (рун), вы можете случайно оторвать диакритический знак от буквы, и на экране появится «битый» символ. Для решения таких сложных задач нормализации текста в Go существует стандартный пакет golang.org/x/text/unicode/norm.

Руны в Golang — это элегантное и эффективное решение для работы с текстом в современном мире. Они позволяют разработчикам абстрагироваться от сложностей кодировки UTF-8 и манипулировать текстом на уровне логических символов, а не сырых байтов.

Главное, что нужно запомнить:

  • Строки в Go — это байты в UTF-8.
  • Руны в Go — это кодовые точки Unicode (int32), представляющие собой цельные символы.
  • Для посимвольной обработки текста всегда используйте руны.
  • Не доверяйте функции len при работе со строками, если в них могут быть не-ASCII символы.

Понимание концепции рун — это важный шаг от новичка к уверенному разработчику на Go, который пишет надежный и корректно работающий код для пользователей из любой точки мира.