Материал проходит вычитку — возможны неточности.
Урок 31 из 118

Регистр — это таблицы

Поиск без учёта регистра сломался на немецких именах: запрос 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», в стандартной библиотеке нет:

Go

Раскрытие в несколько символов умеет 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
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

Запрос STRASSE не находит файл «straße». Что выведет strings.ToUpper("straße")?

Вопрос 2 из 3

Чем регистр в Unicode отличается от ASCII?

Вопрос 3 из 3

Что такое grapheme cluster?

Итоги

Регистр — это таблицы, а не арифметика. Текст обрабатывается рунами (unicode.ToUpper), полное преобразование регистра («ß» → «SS») живёт в x/text/cases, а понятие «видимый символ» шире, чем code point.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки