Что такое rune в Go
Что проверяют
Понимаете ли вы, что строка в Go это неизменяемая последовательность байтов в UTF-8, а не массив символов. Вопрос простой на вид, но ответ сразу показывает, встречался ли кандидат с кириллицей и эмодзи в проде или работал только с латиницей.
Как отвечать
rune это псевдоним типа int32, он хранит кодовую точку Unicode. Парный ему byte это псевдоним uint8, то есть один байт строки. Строковый литерал лежит в памяти в UTF-8, где кодовая точка занимает от одного до четырёх байт.
Отсюда всё поведение:
len(s)возвращает число байтов, а не символов, для символов естьutf8.RuneCountInString(s);s[i]даёт байт типа byte;rangeпо строке декодирует UTF-8 и отдаёт пару: смещение в байтах и руну;[]rune(s)создаёт новый срез с декодированием, по нему уже можно индексировать и резать;- невалидный байт превращается в
utf8.RuneError, то есть U+FFFD.
s := "Привет"
fmt.Println(len(s), utf8.RuneCountInString(s))
for i, r := range s {
fmt.Printf("%d %c %d\n", i, r, r)
}
fmt.Println(string([]rune(s)[:3]))Сильный ответ добавляет, что руна это ещё не то, что пользователь считает символом: буква с комбинирующим знаком или эмодзи с модификатором занимают несколько кодовых точек, и разворот строки по рунам такой кластер разрушит.
Где спотыкаются
Формулируют как «rune это символ UTF-8» или «четырёхбайтовая кодовая точка UTF-8». Кодовая точка принадлежит Unicode, UTF-8 это способ записать её байтами. Сама руна всегда занимает четыре байта как int32, а в строке тот же символ может занимать один.
Режут строку по байтовым индексам ради обрезки текста и получают битый хвост: у кириллической буквы отрезается половина, и наружу уходит вопросительный ромб.
Считают, что индекс в range это порядковый номер символа, и строят по нему нумерацию. Либо наоборот, конвертируют строку в []rune ради одного прохода и выделяют лишнюю память там, где хватило бы range.