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

Строка — это байты

Сергей передал Мише валидацию имён файлов. Первый же тест упал на кириллице: счётчик насчитал в «привет» двенадцать символов вместо шести. Миша пошёл смотреть, что на самом деле лежит в строке.

Главная мысль

в Go строка — неизменяемая последовательность байтов, поэтому len считает байты, а s[i] отдаёт байт, а не символ.

В большинстве языков, из которых приходят в Go, строка — это в том или ином виде массив символов: взял символ по индексу, узнал длину, всё сошлось. В Go эта привычка ломается сразу, и лучше сломать её здесь, пока она не потянула за собой неверную картину всего остального языка.

Строка в Go — это неизменяемая последовательность байтов. Не символов и не «букв», а именно байтов. Всё, что выглядит странным при первом знакомстве, вытекает из этой фразы.

В стандартной реализации строка внутри устроена как два слова: указатель на массив байтов и длина этого массива. Сам массив лежит где-то в куче или в статической памяти, а когда вы передаёте строку в функцию, копируются только эти два слова. Поэтому строки дёшево передавать и возвращать.

ASCII

Пока в строке только латиница, разницы между «байтами» и «символами» не видно — один символ занимает один байт.

Go

Из «последовательности байтов» сразу следует, что len(s) возвращает количество байтов, а s[i] — это i-й байт, а не i-й символ. Тип у него byte, псевдоним uint8.

Кириллица

Разница появляется, как только строка перестаёт быть ASCII. Go хранит строки в кодировке UTF-8, где один символ может занимать от одного до четырёх байтов.

Go

Каждая кириллическая буква здесь занимает два байта. s[0] возвращает первый байт первой буквы, и попытка напечатать его как символ даёт бессмыслицу. Это не ошибка Go — вы сами попросили байт. Индексация по строке всегда работает с байтами, и это гарантировано спецификацией, а не деталь реализации.

В следующем уроке

Вы узнаете:

  • как по первому байту понять, сколько байтов занимает символ UTF-8
  • как len(s) соотносится с видимой длиной строки
Задание · вопросы

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

Вопрос 1 из 3

Валидатор «Полки» считает длину имени. Что вернёт len("привет")?

Вопрос 2 из 3

Для строки s := "привет" чему равен s[0]?

Вопрос 3 из 3

Сколько байтов может занимать один символ в UTF-8?

Итоги

Строка — байты в кодировке UTF-8. len и индексация работают с байтами: для ASCII это незаметно, для кириллицы и прочего Unicode — критично. Дальше разберёмся, что значит неизменяемость и что из неё следует.

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