Фигурные скобки в Rust: как вывести { и } в format! без ошибок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему Rust не любит фигурные скобки?

Если вы когда-нибудь писали на Rust и пытались вывести что-то вроде { "name": "Rust" } через println!, то наверняка видели это:

println!("{ "name": "Rust" }"); // Ошибка компиляции!

Компилятор злорадно сообщает: "invalid reference to argument 'name' — no argument named 'name' found". И вы остаётесь в недоумении: как же вывести фигурные скобки в строке? Ведь в Python это делается через {{ }}, но там всё проще. В Rust своя философия: фигурные скобки — это не просто символы, а часть мощной системы форматирования, заимствованной из Python, но с нюансами.

В этой статье мы разберём все способы экранирования фигурных скобок в Rust: от базового {{ }} до хитростей с Debug и raw string literals. Вы узнаете, как выводить JSON, генерировать код и работать с шаблонами, не попадая в ловушки компилятора.

Почему фигурные скобки — особенные?

В Rust макросы format!, println!, write! и format_args! используют фигурные скобки как маркеры подстановки. Например:

let name = "Rust";
println!("Hello, {}!", name); // Hello, Rust!

Здесь {} — это placeholder, который заменяется на значение переменной. Но что если вам нужен сам символ { или }? Rust использует простой трюк: удвоение скобок.

Способ 1: Классическое удвоение {{ и }}

Самый простой и очевидный способ — написать две фигурные скобки подряд. Первая пара {{ превращается в одну {, а }} — в одну }.

Пример 1: Простое экранирование

fn main() {
    let text = format!("Функция main() {{ ... }}");
    println!("{}", text);
    // Вывод: Функция main() { ... }
}

Пример 2: Экранирование с подстановкой

fn main() {
    let lang = "Rust";
    let year = 2015;
    let result = format!("{{ \"lang\": \"{}\", \"year\": {} }}", lang, year);
    println!("{}", result);
    // Вывод: { "lang": "Rust", "year": 2015 }
}

Этот метод работает везде: в println!, write!, format!. Запомните правило: две скобки = одна скобка.

Способ 2: Именованные аргументы и экранирование

Если вы используете именованные аргументы (например, {name}), экранирование тоже работает, но нужно быть внимательным с позицией скобок.

Пример 3: Именованные плейсхолдеры и скобки

fn main() {
    let user = "Alice";
    // Хотим вывести: Привет, {Alice}!
    println!("Привет, {{{}}}!", user);
    // Вывод: Привет, {Alice}!
}

Разберём строку "Привет, {{{}}}!":

  • {{ → выводит {
  • {} → подставляет user
  • }} → выводит }

Важно: количество скобок всегда чётное для экранирования. Если у вас {{{ — это ошибка, так как три скобки не образуют правильную пару.

Способ 3: Использование Debug-формата ({:?})

Иногда вы хотите вывести структуру данных так, чтобы она отображалась с фигурными скобками автоматически. Например, при отладке или генерации JSON.

Пример 4: Вывод структуры через Debug

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 10, y: 20 };
    println!("{:?}", p);
    // Вывод: Point { x: 10, y: 20 }
}

Трейт Debug сам добавляет скобки вокруг полей. Это удобно, если нужно быстро показать состояние объекта. Но если вам нужен именно JSON или другой формат, придётся либо писать свой Display, либо использовать сериализацию (например, serde_json).

Пример 5: Кастомный Display для точного контроля

use std::fmt;

struct User {
    name: String,
    age: u8,
}

impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{{ \"name\": \"{}\", \"age\": {} }}", self.name, self.age)
    }
}

fn main() {
    let u = User { name: "Bob".into(), age: 30 };
    println!("{}", u);
    // Вывод: { "name": "Bob", "age": 30 }
}

Обратите внимание: внутри write! мы снова используем удвоение скобок — {{ и }}.

Способ 4: Raw string literals (r#...#)

Rust поддерживает сырые строковые литералы — строки, в которых escape-последовательности (вроде \n) не обрабатываются. Но они не отключают форматирование! Сырые строки r"..." всё равно требуют экранирования фигурных скобок, если вы используете их внутри format!.

Пример 6: Raw string + форматирование

fn main() {
    let path = r"C:\Users\Admin";
    // В raw строке \n не обрабатывается, но {} — это placeholder
    println!("Path: {}", path); // Работает: Path: C:\Users\Admin

    // А вот так — ошибка:
    // let s = format!(r"{{ }}"); // Тоже требует удвоения!
}

Если вы хотите полностью избежать обработки фигурных скобок, используйте r#"..."# и не передавайте строку в format!. Просто выведите её как есть.

fn main() {
    let raw = r#"{"key": "value"}"#;
    println!("{}", raw);
    // Вывод: {"key": "value"}
}

Это идеальный вариант для шаблонов, JSON-фрагментов или кода на других языках.

Способ 5: Генерация кода и шаблоны

На практике часто нужно генерировать код на Rust или другом языке. Здесь экранирование становится настоящим искусством.

Пример 7: Генерация Rust-кода

fn main() {
    let func_name = "hello                

Блоги

Рекомендаций по книгам