Рано или поздно по мере освоения возможностей языка Rust встает вопрос: есть ли на нем условная компиляция, наподобие директив препроцессора #if, #ifdef, #elif, #else, #endif языка C? Это очень распространенная проблема для разработчиков, переходящих из C/C++ на Rust.
Короткий ответ: нет. На языке Rust нет препроцессора с директивами в стиле #ifdef или #if, работающих на том же уровне, что и на C/C++.
Однако Rust обладает мощной и более интегрированной системой для условной компиляции, которая служит той же цели. Это реализовано через атрибуты и cfg-систему (от слова configuration).
[Rust-эквивалент препроцессора: #[cfg(...)]
Вместо отдельного шага препроцессора, Rust обрабатывает условную компиляцию на уровне языка с помощью атрибутов. Самый важный из них это #[cfg(...)].
Вот как концепция C/C++ выглядит по сравнению с Rust:
1. #ifdef FEATURE / #if defined(FEATURE) на языке C:
#ifdef UNIX printf("Работа в среде Unix\n"); #endif
Аналог на Rust:
#[cfg(unix)]
{ println!("Работа в среде Unix");
}
Или для функции:
#[cfg(unix)] fn unix_specific_function() { // ...
}
2. #ifndef FEATURE (if NOT defined) на языке C:
#ifndef WINDOWS printf("Работа не в среде Windows\n"); #endif
Аналог на Rust:
#[cfg(not(windows))]
{ println!("Работа не в среде Windows");
}
3. #else на языке C:
#ifdef UNIX printf("Unix\n"); #else printf("Not Unix\n"); #endif
Аналог на Rust:
#[cfg(unix)]
{ println!("Unix");
} #[cfg(not(unix))]
{ println!("Not Unix");
}
4. #elif (else if) на языке C:
#if defined(UNIX) // ... #elif defined(WINDOWS) // ... #else // ... #endif
Аналог на Rust:
#[cfg(unix)] { println!("Unix");
} #[cfg(windows)] { println!("Windows");
} #[cfg(not(any(unix, windows)))] { println!("Какая-то другая OS");
}
На Rust есть несколько общих предопределенных условий для cfg:
• Операционные системы: windows, unix, linux, macos • Архитектуры: target_arch = "x86_64", target_arch = "aarch64" • Семейство: target_family = "unix", target_family = "windows" • Разрядность указателя (Pointer Width): target_pointer_width = "64"
Пользовательские фичи: вы их можете определить самостоятельно (см. далее).
[Определение ваших собственных фич (наподобие -D компилятора GCC)]
На языке C вы можете компилировать код с -DDEBUG_MODE. На Rust вы определяете пользовательские фичи в своем файле Cargo.toml:
[features] default = [] # фичи, разрешенные по умолчанию advanced_mode = [] # ваша собственная фича special_algorithm = [] # другая пользовательская фича
Затем, в вашем коде Rust:
// Эта функция компилируется только если разрешена фича "advanced_mode": #[cfg(feature = "advanced_mode")] fn do_advanced_thing() { // Здесь находится комплексная логика
}
Вы можете также использовать это совместно с другими условиями:
#[cfg(all(unix, feature = "special_algorithm"))] fn unix_specific_algorithm() { // ...
}
Чтобы запустить сборку с разрешенной фичей, используйте Cargo с опцией --features, например:
$ cargo build --features "advanced_mode"
.. или для релиза:
$ cargo build --release --features "advanced_mode"
[Макрос cfg! для условной проверки runtime]
Атрибут #[cfg] удаляет код во время компиляции. Если вам нужно проверять наличие фичи runtime (код скомпилирован, но есть ветвления кода), то используйте макрос cfg!. Он возвратит bool.
if cfg!(target_os = "macos") { println!("Мы работаем на macOS!");
} else { println!("Мы работаем не на macOS.");
} // Обе ветки if компилируются в бинарный код, // и во время выполнения (runtime) выполнится // только одна из них.
[Общие выводы: ключевые отличия от C]
Концепция |
C/C++ |
Rust |
Синтаксис |
Директивы препроцессора (#if, #ifdef) |
Атрибуты (#[cfg(...)]) |
Интеграция |
Отдельная фаза замены текста исходного кода |
Интегрировано в сам язык |
Область действия |
На базе файла, в текстовом режиме |
Семантическое применение к элементам кода (функции, структуры, импорт) |
Пользовательские флаги |
#define, флаг компилятора -D |
[features] в файле Cargo.toml |
Проверка runtime |
Невозможна |
С помощью макроса cfg!(...) |
Система условной компиляции Rust считается в основном более безопасной и надежной, поскольку она понимает структуру кода, защищая от общих ошибок препроцессора наподобие несоответствующих директив #endif. |