Как итерироваться по символам строки в Rust? Перебор символов по индексу
Проблема индексации строк в Rust
В Rust строки хранятся в кодировке UTF-8, поэтому каждый символ может занимать от 1 до 4 байт. Прямая индексация по байтовому индексу (как вы пытались) небезопасна — можно попасть в середину многобайтового символа. Именно поэтому компилятор запрещает писать text[i].
Правильный способ — использовать метод .chars(), который возвращает итератор по Unicode-символам (тип char). Каждый char занимает 4 байта и гарантированно представляет один валидный символ.
Базовое решение: итерация по символам
fn main() {
let text = String::from("Привет, мир! 🦀");
for c in text.chars() {
println!("Символ: '{}'", c);
}
}
Этот код выведет каждый символ по одному, включая кириллицу, знаки препинания и эмодзи. chars() корректно обрабатывает многобайтовые символы.
Получение индекса символа
Если вам нужна позиция символа в байтах (не в символах), используйте .char_indices():
fn main() {
let text = String::from("Привет");
for (index, c) in text.char_indices() {
println!("Индекс {}: символ '{}'", index, c);
}
// Вывод:
// Индекс 0: символ 'П'
// Индекс 2: символ 'р'
// Индекс 4: символ 'и'
// и т.д.
}
Обратите внимание: индексы — это байтовые позиции, а не номера символов. Для кириллицы каждый символ занимает 2 байта, поэтому индексы идут через 2.
Работа со срезами строк
Если вам нужно получить подстроку по индексам, используйте срезы только на границах символов:
fn main() {
let text = "Привет, мир!";
let chars: Vec<char> = text.chars().collect();
// Берём первые 3 символа
let first_three: String = chars[..3].iter().collect();
println!("{}", first_three); // "При"
// Или с помощью char_indices
if let Some((start, _)) = text.char_indices().nth(0) {
if let Some((end, _)) = text.char_indices().nth(3) {
println!("{}", &text[start..end]); // "При"
}
}
}
Производительность и альтернативы
Метод .chars() — самый простой и читаемый. Если вы работаете с ASCII-строками, можно использовать .as_bytes() для прямого доступа к байтам:
fn main() {
let text = "hello";
for &byte in text.as_bytes() {
println!("Байт: {}", byte);
}
}
Но это работает только для ASCII. Для Unicode используйте .chars() — он оптимизирован и не создаёт лишних аллокаций.
Рекомендация
- Используйте
.chars()для перебора символов - Используйте
.char_indices(), если нужны позиции - Избегайте индексации строк через
[]— это ошибка компиляции - Для срезов строк всегда проверяйте границы символов
Альтернативный подход: работа с графемами (Unicode-кластерами)
Метод .chars() итерируется по отдельным Unicode-символам (скалярным значениям). Однако в некоторых языках символ может состоять из нескольких char. Например, эмодзи "👨👩👧👦" (семья) состоит из 7 char, но визуально воспринимается как один символ.
Для работы с такими составными символами используйте крейт unicode-segmentation:
use unicode_segmentation::UnicodeSegmentation;
fn main() {
let text = "Привет 👨👩👧👦!";
for grapheme in text.graphemes(true) {
println!("Графема: '{}'", grapheme);
}
}
Метод graphemes(true) возвращает итератор по "графемным кластерам" — визуальным единицам текста. Это полезно для:
- Обработки текста на азиатских языках
- Работы с эмодзи и комбинирующими символами
- Создания редакторов текста или полей ввода
Добавьте зависимость в Cargo.toml:
[dependencies]
unicode-segmentation = "1.10"
Если вам нужно просто перебрать символы английского или русского текста, .chars() будет достаточно. Для сложного Unicode используйте графемы.
Ответов пока нет.
Войдите чтобы оставить ответ