Emoji и составные emoji
Клиент «ФинГуру» поставил эмодзи семьи 👨👩👧 в название команды. Счётчик символов в интерфейсе показал пять значков вместо одного, и клиент написал в поддержку. Тикет упал Мише.
один видимый эмодзи — это часто не одна руна, а цепочка code point'ов (ZWJ, модификаторы тона, вариационные селекторы, флаги), поэтому len, []rune и RuneCountInString видят его по кусочкам.
Руна — это code point, и предыдущий урок закончился на том, что видимый символ не всегда совпадает с code point'ом. Эмодзи — самый наглядный пример: один «смайлик» на экране может быть одним, двумя, пятью и даже семью code point'ами.
Один эмодзи — четыре байта
Большинство эмодзи лежат вне базовой плоскости Unicode (code point'ы больше U+FFFF), поэтому в UTF-8 каждый занимает четыре байта:
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Тут всё «честно»: один code point — одна руна. Это самый простой случай, и на нём ничего не ломается.
Модификатор тона кожи
Добавим модификатор тона. «👍🏽» — это два code point'а: сам палец U+1F44D и модификатор U+1F3FD. Человек видит один символ, Go — две руны:
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
ZWJ-последовательности
Самый коварный случай — составные эмодзи, склеенные специальным невидимым символом ZWJ (U+200D, zero width joiner). Семья «👨👩👧» — это пять code point'ов: 👨 + ZWJ + 👩 + ZWJ + 👧.
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
range честно переберёт все пять рун — но ни одна из них по отдельности не является тем, что видит пользователь.
Пример 4 (неочевидное): руны ломаются на составных эмодзи
Всё, что построено на рунах, перестаёт работать на составных эмодзи. Разворот строки через []rune из урока развернёт «👨👩👧» в «👧👩👨» — порядок рун изменится, и вместо семьи получится бессмыслица. Для «👍🏽» модификатор тона встанет перед пальцем, и символ сломается.
Флаги — ещё одна ловушка: «🇷🇺» это два regional indicator symbol'а (U+1F1F7 и U+1F1FA), которые только вместе образуют флаг. У «1️⃣» — три code point'а: цифра, вариационный селектор U+FE0F и keycap U+20E3.
Вы узнаете:
- как вариационный селектор U+FE0F превращает «1» в «1️⃣»
- чем ручная склейка кластеров отличается от
uniseg
Проверьте себя
Что произойдёт при развороте "👍🏽" через []rune?
Сколько рун в составном эмодзи "👨👩👧"?
Почему len("👍") == 4, а utf8.RuneCountInString("👍") == 1?
правильных ответов
«Символ» в Go — это руна, один code point. Видимый символ может быть последовательностью рун: эмодзи с тоном — две, ZWJ-семья — пять, флаг — две. Поэтому для задач «сколько значков видит пользователь» или «разбить на видимые символы» рун недостаточно — нужна сегментация по grapheme clusters, и готового обхода по ним в стандартной библиотеке нет (обычно берут github.com/rivo/uniseg).
10 000 ₽ · 60 минут · онлайн