Что скрывается за таинственным r#""# в Rust? Полное объяснение с кодом
Вы когда-нибудь встречали в коде на Rust странную запись вроде r#"..."# и думали: «Что за магический оператор? Это какой-то секретный синтаксис для избранных?» — вы не одиноки. Тысячи разработчиков задавали этот вопрос на Stack Overflow, и сегодня мы раз и навсегда разберёмся, что это такое, зачем это нужно и как использовать сырые строковые литералы (raw string literals) в Rust.
В этой статье мы не просто дадим сухое определение. Мы пройдём путь от проблемы к решению, напишем реальный код, увидим, как r#""# спасает от головной боли, и узнаем, где ещё применяется этот синтаксис. Погнали!
Проблема: когда обычные строки ломают код
Представьте, что вы пишете парсер JSON или регулярное выражение. Внутри строки вам нужно использовать кавычки и обратные слеши. В обычном Rust-коде это выглядит как кошмар:
let json_string = "{\"name\": \"Rust\", \"year\": 2015}";
let regex = "\\d{2}-\\d{2}-\\d{4}"; // дата в формате DD-MM-YYYY
Смотрите на это? Каждый раз, когда вам нужна кавычка внутри строки, вы ставите перед ней обратный слеш (\"). А если вам нужен сам обратный слеш — пишете \\. Код быстро превращается в «лесенку» из слешей, которую трудно читать и отлаживать.
Но это ещё цветочки. А что, если вам нужно вставить в строку символ табуляции? Тогда вы пишете \t. А если вам нужен буквально текст \t? Приходится экранировать: \\t. Глаза начинают болеть уже на второй строке.
Решение: сырые строки — r#""#
Rust предлагает элегантный выход — сырые строковые литералы. Они обозначаются префиксом r и обрамляются в #:
// Сырая строка: всё внутри считается буквальным текстом
let raw_json = r#"{"name": "Rust", "year": 2015}"#;
let raw_regex = r#"\d{2}-\d{2}-\d{4}"#;
Чувствуете разницу? Никакого экранирования! Кавычки внутри — просто кавычки. Обратные слеши — просто слеши. Компилятор Rust воспринимает содержимое между r#" и "# как чистый текст, не пытаясь интерпретировать escape-последовательности.
Это особенно полезно, когда вы работаете с:
- Регулярными выражениями — они полны слешей и спецсимволов.
- JSON, XML, HTML — строки с кучей кавычек.
- SQL-запросами — особенно с вложенными кавычками.
- Путями файлов в Windows —
C:\Users\NameвместоC:\\Users\\Name. - Любыми данными, где обратный слеш должен остаться как есть.
Как это работает под капотом?
Когда Rust встречает r#"..."#, он просто копирует всё, что находится между начальным r#" и конечным "#, в строку без какой-либо обработки. Никаких \n не превращается в перенос строки, никакие \t не становятся табуляцией. Это буквальный срез текста.
Но есть нюанс: что делать, если внутри самой строки встречается последовательность "#? Например, вы хотите вставить текст #"hello"#. Если вы напишете r#"#"hello"#"#, компилятор запутается — где заканчивается литерал?
Для этого в Rust придумали множественные хэши. Вы можете использовать r##"..."##, r###"..."### и так далее. Количество # с обеих сторон должно совпадать:
// Строка содержит внутри "# — используем двойные хэши
let tricky = r##"Строка с "# внутри"##;
println!("{}", tricky); // Выведет: Строка с "# внутри
// А если внутри есть "##? Тогда тройные хэши:
let very_tricky = r###"Текст с "## внутри"###;
println!("{}", very_tricky); // Выведет: Текст с "## внутри
Это напоминает матрёшку: чем больше хэшей вы ставите, тем более сложные последовательности можете включить внутрь. На практике редко нужно больше двух-трёх хэшей, но Rust поддерживает до 255 символов #.
Жизненные примеры: где r#""# реально спасает
1. Регулярные выражения без боли
Вспомните наш пример с датой. С сырыми строками регулярка читается как учебник:
use regex::Regex;
fn main() {
// Сырая строка — никаких двойных слешей!
let date_regex = Regex::new(r#"\d{2}-\d{2}-\d{4}"#).unwrap();
let text = "Сегодня 15-03-2024, а вчера было 14-03-2024";
for date in date_regex.find_iter(text) {
println!("Найдена дата: {}", date.as_str());
}
}
Без r#""# пришлось бы писать "\\d{2}-\\d{2}-\\d{4}". Согласитесь, разница колоссальная.
2. JSON в коде
Часто нужно захардкодить JSON для тестов или конфигурации:
let config = r#"{
"server": {
"host": "127.0.0.1",
"port": 8080,
"ssl": false
},
"logging": {
"level": "debug",
"file": "/var/log/app.log"
}
}"#;
// Теперь config — это готовая строка JSON, которую можно парсить
let parsed: serde_json::Value = serde_json::from_str(config).unwrap();
println!("Порт сервера: {}", parsed["server"]["port"]);
Попробуйте написать то же самое с обычными строками и экранированием — получите мешанину из \", которая сведёт с ума любого код-ревьюера.
3. Windows-пути
Разработчики на Windows знают, как болят глаза от путей вроде "C:\\Users\\Имя\\Documents\\file.txt". С сырыми строками:
let path = r#"C:\Users\Имя\Documents\file.txt"#;
let file = std::fs::read_to_string(path).expect("Файл не найден");
println!("Содержимое: {}", file);
К