Avatar ·

Как в Rust получить количество символов в строке, а не байтов?

Привет, коллеги! Я только начал изучать Rust и столкнулся с неожиданной для себя проблемой. Мне нужно узнать длину строки в символах (char), а не в байтах. В других языках, например, в Python или Java, метод len() обычно возвращает именно количество символов. Но в Rust всё иначе. Вот пример, который меня смутил: ```rust fn main() { let text = String::from("Привет, мир!"); println!("Длина строки: {}", text.len()); } ``` Я ожидал увидеть 12 (количество символов в строке "Привет, мир!"), но получил 20. Потом я понял, что метод .len() считает байты. В UTF-8 кириллические символы занимают 2 байта, поэтому результат другой. Но мне нужно именно количество символов — для пользовательского интерфейса, проверки длины ввода или подсчёта слов. Подскажите, есть ли в Rust какой-то метод, который возвращает количество символов, а не байтов? Или нужно писать свою реализацию? Буду благодарен за примеры кода. Важно: строка может содержать не только ASCII, но и эмодзи, кириллицу, иероглифы — в общем, любые Unicode-символы. Хочется универсального решения. <<>>

Проблема: байты vs символы в Rust

В Rust строки (String и &str) хранятся в кодировке UTF-8. Метод .len() возвращает количество байтов, а не символов. Это сделано для производительности — байтовый счёт работает за O(1). Но для подсчёта символов (Unicode scalar values) нужно использовать другой подход.

Решение: метод .chars().count()

Самый простой и правильный способ — использовать итератор .chars(), который перебирает символы (char) строки, а затем вызвать .count():

fn main() {
    let text = String::from("Привет, мир! 😊");
    // Подсчёт символов
    let char_count = text.chars().count();
    println!("Символов: {}", char_count); // 14 (включая эмодзи)
    
    // Для сравнения — байтовая длина
    println!("Байтов: {}", text.len());    // 22
}

Важные детали

  • Сложность: .chars().count() работает за O(n), где n — количество байт. Это линейный проход по строке. Для коротких строк это незаметно, но для очень длинных (миллионы символов) стоит учитывать.
  • Unicode и графемы: .chars() считает именно Unicode scalar values (char). Но некоторые "символы" (например, эмодзи 🇷🇺 или буквы с диакритическими знаками, как é) могут состоять из нескольких char. Для подсчёта "видимых" символов (графемных кластеров) используйте крейт unicode-segmentation.
  • Пустая строка: "".chars().count() вернёт 0, как и ожидается.

Пример с графемными кластерами

Если вам нужно считать именно то, что видит пользователь (например, флаг страны — один символ с точки зрения пользователя, но два char в UTF-8), добавьте в Cargo.toml:

[dependencies]
unicode-segmentation = "1.10"

И используйте:

use unicode_segmentation::UnicodeSegmentation;

fn main() {
    let text = "🇷🇺 Привет!";
    // Количество char (Unicode scalar values)
    println!("Chars: {}", text.chars().count()); // 4 (флаг занимает 2 char + пробел + 6 букв)
    
    // Количество графемных кластеров (то, что видит пользователь)
    let graphemes = text.graphemes(true).count();
    println!("Graphemes: {}", graphemes); // 9 (флаг как 1 + пробел + 6 букв + !)
}

Советы для начинающих

  • Не пытайтесь использовать .len() для подсчёта символов — это частая ошибка.
  • Если вы пишете библиотеку, документируйте, что метод считает именно chars, а не байты.
  • Для проверки длины ввода пользователя (например, ограничение в 140 символов) используйте .chars().count() — это корректно обработает эмодзи и иероглифы.

Итог: text.chars().count() — стандартный способ получить количество символов в Rust. Для большинства задач этого достаточно. Если нужно работать с графемами — подключайте unicode-segmentation.

<<>>

Альтернативный подход: использование метода char_indices и ручной подсчёт

Если вы по каким-то причинам не хотите использовать итератор .chars() (например, в очень старом коде или при необходимости одновременно получать индексы), можно сделать подсчёт вручную через char_indices():

fn count_chars(s: &str) -> usize {
    s.char_indices().count()
}

fn main() {
    let text = "Здравствуй, мир!";
    println!("{}", count_chars(text)); // 16
}

Метод char_indices() возвращает итератор кортежей (usize, char), где первый элемент — байтовый индекс начала символа, второй — сам символ. Просто вызывая .count(), мы получаем количество символов.

Когда это может быть полезно?

  • Если вам нужны не только символы, но и их позиции в байтах (например, для обрезки строки по символам).
  • В учебных целях — чтобы лучше понять, как устроены строки в Rust.

Однако для простого подсчёта символов .chars().count() короче и читаемее. Используйте char_indices() только если вам действительно нужны индексы.

Важно: Оба метода работают с Unicode scalar values, а не с графемами. Для подсчёта "видимых" символов (как в первом ответе) используйте крейт unicode-segmentation.