В чем разница байтов и рун в Go и зачем нужны руны?
Что проверяют
Понимаете ли вы, что строка в Go это байты, а не символы, и чем это грозит на кириллице. Следом почти всегда просят сказать, что вернёт len("привет") и что выведет цикл for range по такой строке.
Как отвечать
Начните с устройства строки: это неизменяемая последовательность байтов, обычно в UTF-8, хотя язык этого не гарантирует. Дальше два типа:
byte: псевдонимuint8, один байт.s[i]возвращает именно байт, аlen(s)считает байты. Для"привет"получится 12, потому что каждая кириллическая буква занимает два байта;rune: псевдонимint32, одна кодовая точка Юникода. Нужен, чтобы работать с символом целиком, а не с его куском.
Руны появляются в трёх местах. for i, r := range s декодирует UTF-8 на лету: r это руна, а i это байтовое смещение её начала, поэтому индексы идут с шагом 1, 2 или 3. Преобразование []rune(s) даёт срез, где к символу можно обратиться по номеру, но оно проходит всю строку и выделяет память. Пакет unicode/utf8 считает и разбирает символы без аллокации: utf8.RuneCountInString, utf8.DecodeRuneInString, utf8.ValidString.
Практическое правило: для сети, файлов, хешей и протоколов работайте с []byte и пакетом bytes, а для обрезки и разворота текста, который видит человек, переходите к рунам. Битые последовательности при декодировании превращаются в utf8.RuneError (U+FFFD), об этом тоже стоит сказать.
Где спотыкаются
Режут строку по байтам: s[:10] на русском тексте легко разрывает букву пополам, и в выводе появляется мусор.
Считают, что руна это «символ, который видит пользователь». Буква с отдельным диакритическим знаком или эмодзи с модификатором цвета кожи состоят из нескольких рун, для таких случаев нужна работа с графемами, обычно через сторонние библиотеки.
Думают, что индекс в for range это порядковый номер символа. Это смещение в байтах, и код, который использует его как счётчик букв, ломается на первом же не-ASCII символе.