Avatar ·

Как проверить, содержит ли строка подстроку в Rust?

Всем привет! Я только начал изучать Rust, и у меня возникла проблема со строками. Я привык к Python, где можно просто написать `if "abc" in "abcdef":`, и это работает. Но в Rust я столкнулся с тем, что строки — это не просто последовательности символов, а сложные типы данных. Мне нужно написать программу, которая проверяет, содержит ли введённая пользователем строка определённое слово. Например, я хочу проверить, есть ли подстрока "мир" в строке "Привет, мир!". Я пытался использовать оператор `in`, но компилятор выдаёт ошибку. Я также пробовал сравнивать символы в цикле, но это слишком громоздко и, наверное, неправильно. Вот мой неудачный код: ```rust fn main() { let text = String::from("Привет, мир!"); let search = "мир"; if text.contains(search) { // я думал, что так можно, но это не работает? println!("Найдено!"); } } ``` На самом деле, я не уверен, что метод `contains` существует для `String`. Я видел в документации что-то про `&str`, но не понял разницы. Подскажите, как правильно проверять наличие подстроки в строке? Есть ли какие-то нюансы с кодировкой (UTF-8)? И можно ли делать поиск без учёта регистра? Заранее спасибо за помощь! <<>>

Основное решение: метод contains()

Да, ваш подход с методом contains() абсолютно правильный! В Rust есть метод contains() для строк, который работает как для String, так и для &str. Проблема в вашем коде не в самом методе, а в том, как вы его вызвали. Давайте исправим:

fn main() {
    let text = String::from("Привет, мир!");
    let search = "мир";
    if text.contains(search) {
        println!("Найдено!");
    } else {
        println!("Не найдено");
    }
}

Этот код будет работать и выведет "Найдено!". Метод contains() определён в трейте StrExt и доступен для всех строковых типов. Он принимает аргумент, реализующий трейт Pattern, куда входят &str, char, замыкания и даже другие строки.

Разница между String и &str

String — это владеемая, изменяемая строка в куче. &str — это ссылка на строковый срез, неизменяемый вид на часть строки. Оба поддерживают contains(). Если у вас String, вы можете вызывать метод напрямую, как в примере выше. Если у вас &str, тоже работает:

let text: &str = "Привет, мир!";
if text.contains("мир") {
    println!("Найдено!");
}

Поиск с учётом регистра и без

Стандартный contains() чувствителен к регистру. Для поиска без учёта регистра используйте метод to_lowercase() (или to_uppercase()) для обеих строк:

let text = String::from("Привет, Мир!");
let search = "мир";
if text.to_lowercase().contains(&search.to_lowercase()) {
    println!("Найдено (без учёта регистра)!");
}

Обратите внимание: метод to_lowercase() возвращает новую String, поэтому при частых вызовах это может быть неэффективно. Для однократной проверки это нормально.

Дополнительные методы для работы с подстроками

  • find() — возвращает Option<usize> с индексом первого вхождения подстроки:
if let Some(pos) = text.find("мир") {
    println!("Найдено на позиции {}", pos);
}
  • matches() — возвращает итератор по всем вхождениям (для подсчёта или обработки):
let count = text.matches("мир").count();
println!("Количество вхождений: {}", count);
  • starts_with() и ends_with() — проверяют начало/конец строки:
if text.starts_with("Привет") {
    println!("Строка начинается с 'Привет'");
}

Важное замечание про UTF-8

Rust строки — это UTF-8, поэтому поиск подстроки работает корректно с многобайтовыми символами (русскими, китайскими и т.д.). Однако методы вроде find() возвращают байтовый индекс, а не символьный. Если вам нужна работа с символами (char), используйте итератор .chars():

for (i, c) in text.chars().enumerate() {
    if c == 'м' {
        println!("Символ 'м' на позиции {}", i);
    }
}

Производительность

Метод contains() использует эффективный алгоритм поиска (двухсторонний, на основе алгоритма Two-Way), который работает за O(n+m) в худшем случае. Для большинства задач этого более чем достаточно. Если вам нужен очень быстрый поиск с большими строками, рассмотрите крейт aho-corasick для одновременного поиска множества подстрок.

<<>>

Альтернативный подход: использование регулярных выражений

Если вам нужен более гибкий поиск (например, с шаблонами, повторениями, границами слов), используйте крейт regex. Добавьте его в Cargo.toml:

[dependencies]
regex = "1"

Пример использования:

use regex::Regex;

fn main() {
    let text = "Привет, мир! Мир прекрасен.";
    let re = Regex::new(r"\bмир\b").unwrap(); // \b - граница слова
    if re.is_match(text) {
        println!("Найдено слово 'мир' целиком");
    }
    // Поиск без учёта регистра
    let re_case = Regex::new(r"(?i)мир").unwrap();
    if re_case.is_match(text) {
        println!("Найдено 'мир' (без учёта регистра)");
    }
}

Когда использовать regex вместо contains?

  • Когда нужен поиск по шаблону (например, все слова из 4 букв)
  • Когда нужно игнорировать регистр без создания новых строк
  • Когда нужна замена подстрок с шаблонами

Недостаток: крейт regex добавляет зависимость и увеличивает размер бинарника. Для простой проверки на наличие подстроки лучше использовать встроенный contains().

Ещё один вариант: итераторы и замыкания

Если вам нужно проверить наличие любого символа из набора, используйте any():

let text = "Привет, мир!";
let chars_to_find = ['м', 'и', 'р'];
if chars_to_find.iter().any(|&c| text.contains(c)) {
    println!("Строка содержит хотя бы один из указанных символов");
}

Этот подход полезен для проверки на наличие специальных символов или букв.