Атрибуты в Rust — это метаданные, которые передаются компилятору rustc для управления сборкой, оптимизациями, условной компиляцией и бинарным представлением данных.
Если в прошлых статьях мы уже встречали макроатрибуты при написании макросов в rust-procedural-macros или при разметке структур для C-ABI в rust-unsafe-and-ffi, то в этой статье мы системно разберем работу с атрибутами языка.
1. Анатомия атрибутов: внешние #[...] и внутренние #![...]#
В Rust различают две категории атрибутов по их синтаксису и области применения:
Внешние атрибуты (#[...]): относятся к элементу кода, расположенному непосредственно под ними (функция, структура, модуль, ветка перечисления).
Внутренние атрибуты (#![...]): с восклицательным знаком относятся к контейнеру, внутри которого они объявлены (к текущему файлу, модулю или всему крейту).
#![allow(unused_imports)]// Внутренний атрибут: отключает предупреждение для ВСЕГО файла
#[allow(dead_code)]// Внешний атрибут: отключает предупреждение только для этой структуры
structInternalConfig{id: u64,}
Условная компиляция позволяет отсекать ненужные блоки кода до начала фазы оптимизаций и генерации машинных инструкций. Это незаменимо при написании кроссплатформенного кода под Linux, macOS и Windows.
Атрибут #[cfg(...)] поддерживает логические комбинаторы:
#[cfg(target_os = "linux")] — проверка операционной системы.
#[cfg(all(unix, target_arch = "x86_64"))] — логическое И.
Динамический макрос cfg!(...) позволяет проверять условия компиляции прямо внутри обычных ветвлений if-else.
Ниже представлен пошаговый пример работы с #[cfg] и макросом cfg!:
Условная компиляция #[cfg] и макрос cfg!
Шаг 1/2
1// Шаг 1: Условная компиляция функций с помощью атрибута #[cfg]
2 3// Данная функция скомпилируется только на операционной системе Linux
4#[cfg(target_os = "linux")] 5fnget_os_family()-> &'staticstr{ 6"Linux OS" 7} 8 9// Данная функция скомпилируется только на операционной системе macOS (darwin)
10#[cfg(target_os = "macos")]11fnget_os_family()-> &'staticstr{12"macOS (Darwin)"13}1415// Данная функция скомпилируется только на операционной системе Windows
16#[cfg(target_os = "windows")]17fnget_os_family()-> &'staticstr{18"Windows OS"19}2021// Резервная реализация для всех остальных систем
22#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]23fnget_os_family()-> &'staticstr{24"Другая операционная система"25}2627fnmain(){28println!("Текущая операционная система: {}",get_os_family());29}30
1. Условная компиляция #[cfg]
Атрибут #[cfg(...)] исключает не удовлетворяющий условию код прямо во время парсинга на этапе компиляции.
Поддерживает логические операторы any(...), all(...) и not(...).
Результат выполнения:
1// Шаг 2: Использование динамического макроса cfg!(...)
2fnmain(){ 3// В отличие от атрибута #[cfg], макрос cfg! возвращает bool во время компиляции
4ifcfg!(target_pointer_width="64"){ 5println!("Программа скомпилирована под 64-битную архитектуру"); 6}else{ 7println!("Программа скомпилирована под 32-битную архитектуру"); 8} 910ifcfg!(debug_assertions){11println!("Режим сборки: Debug");12}else{13println!("Режим сборки: Release (оптимизированный)");14}15}16
2. Макрос cfg!(…)
Макрос cfg!(...) оставляет код скомпилированным, но вычисляет булево значение true/false.
Позволяет ветвить логику внутри обычных if-выражений.
Результат выполнения:
3. Оптимизации, линтеры и разметка памяти #[repr(C)]#
Помимо условной компиляции, атрибуты управляют поведением компилятора и линтера:
#[inline] и #[inline(always)]: дают подсказку компилятору встроить тело функции непосредственно в точку вызова, исключая накладные расходы на вызов.