Регулярные выражения в Rust с примерами: на практике
Регулярные выражения (regex) — мощный инструмент для работы с текстом. В Rust, несмотря на строгую типизацию и фокус на безопасности, работа с регулярными выражениями реализована эффективно и удобно благодаря крейту regex. В этой статье мы разберем, как использовать регулярные выражения в Rust на практике: от установки до сложных сценариев обработки строк.
Мы рассмотрим основные операции: поиск, извлечение, замену и валидацию. Все примеры кода написаны на Rust и готовы к использованию.
Установка и подключение крейта regex
Для работы с регулярными выражениями в Rust используется сторонний, но очень популярный крейт regex. Он не входит в стандартную библиотеку, поэтому его нужно добавить в проект.
Добавьте зависимость в файл Cargo.toml:
[dependencies]
regex = "1"
После этого в коде можно импортировать основные типы:
use regex::Regex;
Крейт regex компилирует регулярное выражение в конечный автомат, что обеспечивает высокую производительность. Ошибки компиляции шаблона отлавливаются на этапе выполнения (через Result), что делает код надежным.
Основы: поиск совпадений в строке
Самый простой способ проверить, соответствует ли строка шаблону — использовать метод is_match. Рассмотрим пример проверки наличия email-адреса в тексте.
use regex::Regex;
fn main() {
let re = Regex::new(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b").unwrap();
let text = "Свяжитесь с нами по адресу support@example.com.";
if re.is_match(text) {
println!("Email найден!");
} else {
println!("Email не найден.");
}
}
Метод Regex::new принимает строку с шаблоном и возвращает Result. unwrap() здесь используется для простоты, но в production-коде стоит обрабатывать ошибки корректно.
Для поиска всех совпадений используется метод find_iter, который возвращает итератор по объектам Match:
use regex::Regex;
fn main() {
let re = Regex::new(r"\d+").unwrap();
let text = "Цены: 100, 250 и 500 рублей.";
for mat in re.find_iter(text) {
println!("Найдено число: {}", mat.as_str());
}
}
Этот код выведет:
Найдено число: 100
Найдено число: 250
Найдено число: 500
Извлечение данных с помощью групп захвата
Группы захвата (capturing groups) позволяют извлекать не просто всё совпадение, а его отдельные части. Например, разбор даты в формате "дд-мм-гггг".
use regex::Regex;
fn main() {
let re = Regex::new(r"(\d{2})-(\d{2})-(\d{4})").unwrap();
let text = "Сегодня 15-03-2024.";
if let Some(caps) = re.captures(text) {
println!("День: {}", &caps[1]);
println!("Месяц: {}", &caps[2]);
println!("Год: {}", &caps[3]);
}
}
Индексация групп начинается с 1 (группа 0 — это всё совпадение). Метод captures возвращает Option<Captures>. Если совпадение найдено, мы можем обращаться к группам через индекс.
Для извлечения всех вхождений с группами используется captures_iter:
use regex::Regex;
fn main() {
let re = Regex::new(r"(\w+)@(\w+)\.(\w+)").unwrap();
let text = "emails: user@host.com, admin@site.org";
for caps in re.captures_iter(text) {
println!("Логин: {}, домен: {}, зона: {}", &caps[1], &caps[2], &caps[3]);
}
}
Этот подход незаменим при парсинге логов, файлов конфигурации или извлечении структурированных данных из неструктурированного текста.
Замена текста по шаблону
Крейт regex поддерживает замену текста. Метод replace заменяет первое совпадение, а replace_all — все совпадения.
Простая замена:
use regex::Regex;
fn main() {
let re = Regex::new(r"плохой").unwrap();
let text = "Это был плохой день.";
let result = re.replace(text, "хороший");
println!("{}", result); // Выведет: Это был хороший день.
}
Замена с использованием групп захвата позволяет динамически формировать строку замены:
use regex::Regex;
fn main() {
let re = Regex::new(r"(\w+) (\w+)").unwrap();
let text = "Иван Иванов";
// Меняем местами имя и фамилию
let result = re.replace(text, "$2 $1");
println!("{}", result); // Выведет: Иванов Иван
}
В строке замены $1, $2 и т.д. ссылаются на соответствующие группы захвата. Это мощный инструмент для форматирования данных.
Также можно передавать замыкание для более сложной логики замены:
use regex::Regex;
fn main() {
let re = Regex::new(r"\b\d+\b").unwrap();
let text = "Числа: 10, 20, 30.";
let result = re.replace_all(text, |caps: ®ex::Captures| {
let num: i32 = caps[0].parse().unwrap();
(num * 2).to_string()
});
println!("{}", result); // Выведет: Числа: 20, 40, 60.
}
Валидация строк с помощью регулярных выражений
Регулярные выражения отлично подходят для валидации ввода: проверка email, номера телефона, IP-адреса и т.д. Рассмотрим пример проверки российского номера телефона.
use regex::Regex;
fn validate_phone(phone: &str) -> bool {
// Шаблон для номеров вида +7 (XXX) XXX-XX-XX или 8XXXXXXXXXX
let re = Regex::new(r"^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{