Rust: с нуля до своего органайзера Урок 25 из 35
Текст, UTF-8 и видимые буквы
Урок 25. Различать байты, значения `char` и видимые буквы при работе с названием задачи.
Текст переведён с помощью ИИ.
Предварительно: уроки 3, 9 и 22–24. Если String и &str смешиваются, вернитесь к уроку 22.
Образ и опорная карта
Адрес на бумаге можно считать по строчкам, словам или буквам — это разные вопросы. Компьютер тоже хранит текст и считает его единицы по-разному. UTF-8 кодирует текст в байтах; один видимый знак может занимать несколько байтов. char в Rust — одно скалярное значение Unicode, то есть один допустимый кодовый пункт, а не всегда одна воспринимаемая человеком буква. Графема — видимая единица письма, которая может состоять из нескольких таких значений. Образ ограничен: глазами легко узнать букву, но число её байтов без правил UTF-8 не угадать.
Текст → байты .len() → скалярные значения .chars() → видимые графемы. Скажите вслух, на какой вопрос отвечает каждое число. Метод .chars() последовательно отдаёт значения char; полученную последовательность называют итератором. .count() проходит по ней и считает элементы. Это не подсчёт графем. Устройство итераторов подробно разберём позже; сейчас достаточно знать, что два вызова вместе считают значения char, не создавая новый String.
Одна буква, два байта
В organizer сохраните прежний src/main.rs, замените его целиком и выполните cargo run рядом с Cargo.toml. Другие файлы и зависимости не меняются. Сначала предскажите два числа; блок результата не включает сообщения Cargo.
В коде .len() для String возвращает число байтов, а .chars().count() — число скалярных значений Unicode. Русская Я кодируется UTF-8 двумя байтами, хотя это одно значение char и одна видимая буква.
fn main() {
let title = String::from("Я");
println!("Байты: {}", title.len());
println!("Значения char: {}", title.chars().count());
}
Байты: 2
Значения char: 1
len() не ошибся: он ответил на вопрос о месте хранения, а не о длине слова для читателя. Для латинской A оба счёта равны 1, но по одному такому примеру нельзя делать правило для всего текста.
Когда видимая буква состоит из двух значений
Следующая запись "e\u{301}" — отдельный пример. \u{301} в строковом литерале означает кодовый пункт Unicode 301 в шестнадцатеричной записи; это знак ударения, добавляемый к предыдущей e. Обратная косая черта начинает специальную запись внутри кавычек, u указывает Unicode, а {301} задаёт код. Это не подстановка переменной как {title} в println!. Вместе e и знак выглядят как одна буква, но дают два значения char и три байта UTF-8.
fn main() {
let title = "e\u{301}";
println!("Байты: {}", title.len());
println!("Значения char: {}", title.chars().count());
}
Байты: 3
Значения char: 2
Следовательно, .chars().count() тоже не всегда отвечает на вопрос «сколько видимых букв?». Стандартная библиотека Rust не предоставляет готовый счёт графем; для него нужна отдельная обработка текста. В нашем органайзере не будем ставить ограничение «столько-то букв», пока не выберем точное правило.
Почему нельзя взять title[0]
Индекс ноль не сообщает, хотим мы первый байт, значение char или графему. Русская буква занимает несколько байтов; взятый отдельно первый байт не является целым символом. Поэтому этот отдельный пример отклоняется с E0277:
fn main() {
let title = String::from("Я");
println!("{}", title[0]);
}
Для последовательного чтения значений используйте .chars(). Срезы текста из урока 23 используют байтовые границы: попытка разрезать UTF-8 внутри буквы остановит программу с panic. Пока не вычисляйте такие границы наугад. Для хранения названия по-прежнему подходит String, а для чтения — &str.
Проверка понимания
- Что считает
"Я".len()? - Считает ли
.chars().count()графемы? - Почему
title[0]не является безопасным способом получить первую букву?
Проверьте: байты; нет, значения Unicode char; неизвестно, какую единицу текста должен означать индекс. Воспроизведите опорную карту без страницы.
Задание
Обязательное. Создайте String с Я и литерал "e\u{301}". Для каждого выведите число байтов и значений char в таком порядке: Я: байтов 2, значений char 1 и e с ударением: байтов 3, значений char 2. Рассчитайте оба числа методами, не вписывайте готовые ответы в println!.
Своё наблюдение. Замените Я на A: оба числа станут 1. Верните русский пример.
Подсказка
Для каждого текста используйте .len() и .chars().count() как два аргумента println!. Литерал имеет тип &str, но оба метода работают и для него.
Эталон после попытки
fn main() {
let title = String::from("Я");
let combined = "e\u{301}";
println!("Я: байтов {}, значений char {}", title.len(), title.chars().count());
println!(
"e с ударением: байтов {}, значений char {}",
combined.len(),
combined.chars().count()
);
}
Я: байтов 2, значений char 1
e с ударением: байтов 3, значений char 2
Результат различает место в памяти и число значений, но не объявляет их числом видимых букв. Официальный разбор UTF-8 и строк.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.