Введение: Зачем нужны регулярные выражения в Rust?
Регулярные выражения (regex) — это мощный инструмент для поиска, извлечения и замены текста по шаблону. В Rust работа с регулярными выражениями реализована через крейт regex, который считается одним из самых быстрых и безопасных в экосистеме. В отличие от многих других языков, компилятор Rust проверяет корректность регулярного выражения на этапе компиляции (если использовать макрос regex!), что помогает избежать ошибок во время выполнения.
В этой статье мы разберем практические примеры использования регулярных выражений в Rust: от базового поиска до сложного извлечения данных. Вы узнаете, как настроить проект, писать шаблоны и обрабатывать результаты.
1. Настройка проекта и первый regex
Добавление зависимости
Для начала добавьте крейт regex в файл Cargo.toml:
[dependencies]
regex = "1"
После этого обновите зависимости командой cargo build.
Первый пример: поиск слова
Напишем простую программу, которая находит слово "Rust" в строке:
use regex::Regex;
fn main() {
let re = Regex::new(r"Rust").unwrap();
let text = "Я изучаю Rust и Python.";
if re.is_match(text) {
println!("Слово 'Rust' найдено!");
} else {
println!("Слово не найдено.");
}
}
Функция Regex::new компилирует шаблон. Если шаблон невалиден, программа завершится с ошибкой (используйте unwrap() только в прототипах).
2. Извлечение данных с помощью захватов
Группировка и именованные группы
Регулярные выражения позволяют извлекать части текста с помощью круглых скобок. Рассмотрим пример с email:
use regex::Regex;
fn main() {
let re = Regex::new(r"(?P<name>[a-zA-Z0-9._%+-]+)@(?P<domain>[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})").unwrap();
let text = "Мой email: user@example.com, а также admin@test.org.";
for caps in re.captures_iter(text) {
println!("Имя: {}", &caps["name"]);
println!("Домен: {}", &caps["domain"]);
}
}
В этом примере мы используем именованные группы (?P<name>...). Это делает код более читаемым и удобным для дальнейшей обработки.
Извлечение всех совпадений
Если нужно получить все совпадения без групп, используйте find_iter:
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());
}
}
Метод find_iter возвращает итератор по объектам Match, которые содержат начальную и конечную позиции, а также сам текст совпадения.
3. Замена текста и сложные шаблоны
Простая замена
Заменим все цифры на слово "NUMBER":
use regex::Regex;
fn main() {
let re = Regex::new(r"\d+").unwrap();
let text = "Код: 12345, статус: 0.";
let result = re.replace_all(text, "NUMBER");
println!("{}", result); // "Код: NUMBER, статус: NUMBER."
}
Замена с использованием захватов
Можно ссылаться на группы в строке замены с помощью $1, $2 или именованных групп $name:
use regex::Regex;
fn main() {
let re = Regex::new(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})").unwrap();
let text = "Дата: 2024-03-15";
let result = re.replace_all(text, "$day.$month.$year");
println!("{}", result); // "Дата: 15.03.2024"
}
Сложный шаблон: парсинг логов
Рассмотрим реальный пример — парсинг строки лога веб-сервера:
use regex::Regex;
fn main() {
let log_line = r#"192.168.1.1 - - [10/Mar/2024:12:30:45 +0000] "GET /index.html HTTP/1.1" 200 2326"#;
let re = Regex::new(
r#"^(?P<ip>\d+\.\d+\.\d+\.\d+) - - \[(?P<date>[^\]]+)\] "(?P<method>\w+) (?P<path>[^ ]+) HTTP/\d\.\d" (?P<status>\d{3}) (?P<size>\d+)$"#
).unwrap();
if let Some(caps) = re.captures(log_line) {
println!("IP: {}", &caps["ip"]);
println!("Дата: {}", &caps["date"]);
println!("Метод: {}", &caps["method"]);
println!("Путь: {}", &caps["path"]);
println!("Статус: {}", &caps["status"]);
println!("Размер: {}", &caps["size"]);
}
}
Обратите внимание на экранирование кавычек и использование сырых строковых литералов r#"..."# — это упрощает написание сложных шаблонов.
4. Производительность и лучшие практики
Компиляция regex один раз
Если вы используете одно и то же регулярное выражение многократно (например, в цикле), компилируйте его один раз и сохраняйте в переменной. Компиляция — дорогая операция.
Использование макроса regex!
Макрос regex! компилирует шаблон на этапе компиляции программы. Это гарантирует корректность regex и ускоряет выполнение:
use regex::Regex;
fn main() {
let re = regex!("\\d{3}-\\d{2}-\\d{4}");
let text = "SSN: 123-45-6789";
if re.is_match(text) {
println!("Найден номер SSN.");
}
}
Для использования макроса добавьте regex с feature macro в Cargo.toml:
[dependencies]
regex = { version =