Avatar ·

Как проверить, собран проект в режиме release или debug, используя cfg в Rust?

Всем привет! Я только начал изучать Rust и пишу небольшой проект на Cargo. У меня возникла проблема: мне нужно, чтобы в отладочной (debug) сборке программа выводила много отладочной информации в консоль (например, логи с уровнем TRACE), а в финальной (release) сборке эти сообщения вообще не выводились, чтобы не замедлять работу. Я знаю, что в C/C++ для этого используют препроцессорные директивы вроде `#ifdef DEBUG`. В Rust я слышал про макрос `cfg!()`, но не понимаю, как его правильно применить именно для этой задачи. Вот пример моего текущего кода: ```rust fn main() { let x = 42; println!("x = {}", x); // Здесь я хочу добавить отладочный вывод, который будет только в debug сборке // println!("[DEBUG] x is: {}", x); // Это должно работать только в debug } ``` Я пробовал писать так: `#[cfg(debug_assertions)]`, но это, кажется, работает не совсем так, как я ожидаю. Может быть, нужно использовать `#[cfg(not(release))]`? Или есть какой-то специальный флаг для Cargo? Объясните, пожалуйста, как правильно разделять код для debug и release сборок с помощью условной компиляции в Rust. Заранее спасибо! <<>>

Использование #[cfg(debug_assertions)] и #[cfg(not(debug_assertions))]

В Rust нет встроенного флага debug или release напрямую для условной компиляции. Вместо этого используется ключевое слово debug_assertions. Оно автоматически включено, когда компилятор не оптимизирует код (то есть в режиме debug по умолчанию) и отключено при оптимизации (режим release). Это самый правильный и идиоматичный способ.

Вот как это работает на практике:

fn main() {
    let x = 42;
    println!("x = {}", x);

    // Этот блок кода будет скомпилирован ТОЛЬКО в debug сборке
    if cfg!(debug_assertions) {
        println!("[DEBUG] x is: {}", x);
        println!("[DEBUG] Сейчас выполняется отладочная сборка!");
    }

    // Альтернативный вариант с атрибутом для целой функции
    #[cfg(debug_assertions)]
    {
        println!("[DEBUG] Ещё один отладочный вывод");
        // Здесь можно делать много действий
    }
}

Обратите внимание на разницу между cfg!() (макрос, возвращающий bool) и #[cfg()] (атрибут, который полностью исключает код из компиляции).

  • cfg!(debug_assertions) — вычисляется во время компиляции и возвращает true или false. Код внутри if остаётся в бинарнике, но не выполняется, если условие ложно. Однако компилятор обычно оптимизирует «мёртвый» код, так что это безопасно.
  • #[cfg(debug_assertions)] — атрибут, который полностью удаляет элемент (строку, блок, функцию) из сборки, если условие не выполнено. Это более агрессивный подход, который гарантирует, что код не попадёт в release-версию.

Проверка на release сборку

Если вам нужно сделать что-то только в release, используйте отрицание:

#[cfg(not(debug_assertions))]
fn release_only_function() {
    println!("Этот код только в release сборке");
}

Как это проверить?

Запустите обычную сборку:

cargo run
# Вы увидите отладочные сообщения

Затем release сборку:

cargo run --release
# Отладочные сообщения исчезнут

Важный нюанс: пользовательские профили

Если вы создали свой профиль сборки в Cargo.toml (например, [profile.release] или [profile.dev]), то debug_assertions всё равно будет работать по умолчанию. Но будьте осторожны: если вы вручную отключите debug-assertions в debug профиле (что странно, но возможно), то код перестанет компилироваться в debug. Обычно это не нужно.

Также есть более продвинутый способ — использовать пользовательские флаги cfg через build.rs или переменные окружения, но для 99% задач debug_assertions — это именно то, что нужно.

Итог: используйте cfg!(debug_assertions) для простых проверок внутри функций и #[cfg(debug_assertions)] для целых блоков или отдельных функций. Это стандартный и документированный способ в Rust.

<<>>

Альтернативный подход: использование build.rs и пользовательских флагов

Если вам нужно более гибкое управление, чем просто debug/release (например, три режима: debug, release и профилирование), вы можете задать собственные флаги условной компиляции через скрипт сборки build.rs.

Создайте файл build.rs в корне вашего проекта:

fn main() {
    // Проверяем, собирается ли проект с профилем "release"
    if std::env::var("PROFILE").unwrap() == "release" {
        println!("cargo:rustc-cfg=my_release");
    } else {
        println!("cargo:rustc-cfg=my_debug");
    }
}

Теперь в коде вы можете использовать свои флаги:

#[cfg(my_debug)]
fn debug_function() {
    println!("Работает только в debug (по нашему определению)");
}

#[cfg(my_release)]
fn release_function() {
    println!("Работает только в release");
}

Плюсы: полный контроль, можно добавить любые условия (например, feature = "verbose").

Минусы: нужно писать и поддерживать build.rs, что избыточно для простой задачи debug/release. Для большинства случаев лучше использовать встроенный debug_assertions.

Также помните, что переменная PROFILE может быть не единственной; вы можете проверять OPT_LEVEL (уровень оптимизации) или DEBUG (включены ли отладочные символы).