Регистр — это таблицы
Поиск без учёта регистра сломался на немецких именах: запрос STRASSE не находил файл «straße». Тикет пришёл от поддержки, и Сергей перебросил его Мише. Аня на разборе показала, где именно расходится регистр.
в Unicode понятие «заглавная буква» не сводится к вычитанию константы — регистр живёт в таблицах, а правильная обработка текста идёт через руны.
Побайтовая asciiUpper из урока работала, потому что для ASCII сдвиг на 'a'-'A' — верная операция. В Unicode понятие «заглавная буква» уже не сводится к вычитанию константы: регистр знают таблицы, и у части символов пары просто нет.
Обработка через руны и таблицы
Обрабатывать текст нужно рунами — функция принимает руну и возвращает руну:
import (
"strings"
"unicode"
)
func upper(s string) string {
var b strings.Builder
for _, r := range s {
b.WriteRune(unicode.ToUpper(r))
}
return b.String()
}
unicode.ToUpper('й') вернёт 'Й', и для кириллицы этого достаточно. strings.Builder нужен потому, что итоговый размер заранее неизвестен, а WriteRune сам закодирует руну обратно в UTF-8. Обычно хватает готового strings.ToUpper, но ручная версия показывает механизм: текст обрабатывается рунами, а не байтами.
Граница простого сопоставления
Одна оговорка: unicode.ToUpper делает простое сопоставление, руна в руну. Полного преобразования регистра, где у «ß» заглавная форма — две буквы «SS», в стандартной библиотеке нет:
Что здесь показано
- Стек — локальные переменные функции.
i = 3— значение;p = ptr → 10— указатель на значение;b = "hello"— срез (заголовок: указатель + длина + ёмкость). - Куча — данные, на которые ссылаются переменные:
rodata— байты строки-литерала (в бинарнике),данные N Б— бэкинг-массив слайса,строка— динамическая строка. - Горутины — лёгкие потоки Go: статус (running/runnable/waiting…), функция, в которой каждая сейчас находится, размер стека (растёт по мере работы) и пометка «из main» — горутина запущена из твоего кода.
- GC — сборщик мусора: фаза (off/mark) и число завершённых циклов.
Раскрытие в несколько символов умеет golang.org/x/text/cases — cases.Upper(language.Und).String("straße") даёт «STRASSE». В повседневном коде до него доходит редко.
Граница понятия «символ»
Руна — это code point, и для большинства задач это правильная единица: поиск, сравнение, разбиение по «буквам». Но видимый пользователем символ не всегда совпадает с code point. «е́» — это «е» плюс отдельный combining-акцент: два code point'а, а человек видит один значок. Такой видимый символ называется grapheme cluster, и готового обхода по ним в стандартной библиотеке нет — обычно берут github.com/rivo/uniseg. Рядом живёт golang.org/x/text/unicode/norm для нормализации: «е» с combining-акцентом и готовый символ «é» (U+00E9) выглядят одинаково, но как строки не равны, и нормализация приводит их к одной форме.
Для уроков и 99% рабочего кода рун достаточно; просто держи в голове, что «символ» в Go строго означает «code point», и когда понадобится полноценная текстовая сегментация, она будет строиться поверх рун, а не вместо них. Самый яркий пример того, что руны — это ещё не видимые символы, — составные эмодзи: о них следующий урок.
Вы узнаете:
- как устроены составные emoji и ZWJ-последовательности
- что такое grapheme clusters и как их нормализуют
- как работают языковые параметры в
cases
Проверьте себя
Запрос STRASSE не находит файл «straße». Что выведет strings.ToUpper("straße")?
Чем регистр в Unicode отличается от ASCII?
Что такое grapheme cluster?
правильных ответов
Регистр — это таблицы, а не арифметика. Текст обрабатывается рунами (unicode.ToUpper), полное преобразование регистра («ß» → «SS») живёт в x/text/cases, а понятие «видимый символ» шире, чем code point.
10 000 ₽ · 60 минут · онлайн