Как условно включать атрибуты вроде `#[derive]` в Rust?
Почему нельзя просто поставить `cfg` на `derive`?
Атрибуты в Rust бывают двух типов: инертные (например, #[doc]) и процедурные (например, #[derive(Debug)], #[cfg]). #[derive] — это процедурный макрос, который выполняется на этапе компиляции и генерирует код. Проблема в том, что #[cfg] сам является процедурным атрибутом, и они не могут быть вложены друг в друга напрямую. Компилятор обрабатывает атрибуты последовательно, и #[cfg(debug_assertions)] не может «отключить» #[derive(Debug)], потому что сначала применяется derive, а потом cfg уже не имеет смысла.
Решение 1: Условная компиляция через макросы (рекомендуется)
Используйте cfg_attr — специальный атрибут, который применяет другой атрибут только при выполнении условия. Это стандартный способ для вашей задачи:
#[cfg_attr(debug_assertions, derive(Debug))]
struct MyStruct {
value: i32,
}
Как это работает:
cfg_attrпринимает два аргумента: условие и атрибут, который нужно применить.- Если условие истинно (например,
debug_assertionsвключено), тоderive(Debug)применяется к структуре. - Если условие ложно (релизная сборка), атрибут
derive(Debug)игнорируется.
Полный пример:
#[cfg_attr(debug_assertions, derive(Debug))]
struct MyStruct {
value: i32,
}
fn main() {
let s = MyStruct { value: 42 };
// В debug-сборке это скомпилируется, в release — нет
#[cfg(debug_assertions)]
println!("{:?}", s);
}
Решение 2: Условная реализация вручную
Если нужно больше контроля (например, реализовать несколько трейтов или добавить методы), можно использовать cfg на уровне impl-блока:
struct MyStruct {
value: i32,
}
#[cfg(debug_assertions)]
impl std::fmt::Debug for MyStruct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MyStruct")
.field("value", &self.value)
.finish()
}
}
Это эквивалентно derive(Debug), но даёт гибкость для кастомной реализации. Однако для простых случаев cfg_attr лаконичнее.
Решение 3: Использование feature-флагов (для библиотек)
Если вы пишете библиотеку и хотите дать пользователям возможность включать Debug по требованию, используйте Cargo features:
// Cargo.toml
[features]
debug-impl = []
// lib.rs
#[cfg_attr(feature = "debug-impl", derive(Debug))]
pub struct MyStruct {
value: i32,
}
Тогда пользователь может включить debug-impl в своём Cargo.toml: my_crate = { features = ["debug-impl"] }.
Важные замечания
cfg_attrработает с любыми атрибутами, не только сderive. Например:#[cfg_attr(windows, path = "win.rs")].- Условия могут быть сложными:
#[cfg_attr(all(debug_assertions, feature = "extra"), ...)]. - Не путайте
cfg_attrсcfg: первый — это атрибут, который применяет другой атрибут; второй — условная компиляция целых блоков.
Начиная с Rust 1.0, cfg_attr стабилен и является идиоматическим решением для условных атрибутов. Ваш код на современном Rust (edition 2021) будет работать без проблем.
Альтернативный подход: Использование трейта-заглушки
Если по каким-то причинам cfg_attr не подходит (например, вы работаете с устаревшей версией Rust или макросом, который не поддерживает cfg_attr), можно создать трейт-заглушку через условную компиляцию:
#[cfg(debug_assertions)]
use std::fmt::Debug;
#[cfg(not(debug_assertions))]
trait Debug {}
#[cfg_attr(debug_assertions, derive(Debug))]
struct MyStruct {
value: i32,
}
// Теперь код с Debug будет компилироваться в обоих режимах,
// но в release режиме трейт Debug — пустой и не генерирует код.
Этот метод работает, но менее надёжен, так как требует ручного определения трейта и может конфликтовать с другими зависимостями. Используйте его только если cfg_attr по какой-то причине недоступен (например, при работе с очень старыми версиями Rust до 1.0).
Дополнительный совет: Измерение производительности
Если вы беспокоитесь о размере кода: derive(Debug) в релизной сборке не добавляет никаких накладных расходов, если вы не вызываете println!("{:?}", ...). Но если вы вызываете его условно (через #[cfg(debug_assertions)] на месте вызова), то компилятор выбросит мёртвый код. Так что ваша первоначальная мотивация верна — используйте cfg_attr на структуре и cfg на месте использования, чтобы избежать лишних зависимостей.
Ответов пока нет.
Войдите чтобы оставить ответ