Rust的#[cfg()]属性是一个强大的功能,它允许开发人员在编译时根据指定的条件有条件地编译代码。它支持在编译过程中对代码进行细粒度控制,从而产生更高效和优化的程序。在其核心,#[cfg()]是一个编译时属性宏(与类函数宏相反)。它允许Rust在编译期间计算谓词并决定是否包含或排除特定的代码块或项。我们将通过多个示例介绍如何表达谓词以及在何处使用此属性。
条件
Rust提供了几个内置选项,可以与#[cfg()]一起使用,以定义哪些条件可以允许将代码包含在构建中。
目标操作系统
#[cfg(target_os = "android")]
fn connect_to_instant_apps() {
// ...
}
目标架构
#[cfg(target_arch = "x86_64")]
fn target_system_config() -> Option<SystemConfig> {
// ...
}
功能标志
使用功能选项,开发人员可以根据是否启用特定的功能标志有条件地编译代码。这在构建带有可选特性的库或应用程序时特别有用。
#[cfg(feature = "server")]
fn render(nodes: Vec<Node>) -> impl View {
// ...
}
调试模式
debug_assertions选项允许以调试模式选择性地编译代码。
#[cfg(debug_assertions)]
fn debug_payload(payload: &Payload) {
// Code runs only in debug mode
}
测试
#[cfg(test)]
fn save(&self) {
// Don't persist since this isn't production code
}
组合
#[cfg()]具有使用各种组合处理复杂条件的能力,同时仍然保持可读。
All
例如,all()使用逻辑AND组合多个条件,确保所有条件的计算值必须为true才能编译相关代码。
// 仅在为32位体系结构的类unix操作系统时编译
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() {
// ...
}
Any
另一方面,any()使用逻辑OR组合多个条件,允许在任何指定条件为真时编译代码。
// 这将在针对Android或iOS时编译
#[cfg(any(target_os = "android", target_os = "ios"))]
fn on_mobile() {
// ...
}
Not
此外,not()将条件(或一组条件)取反,从而允许在指定条件为false时编译代码。这是与前一个函数相反的函数:
// 当不是Android或iOS时将编译
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn not_on_mobile() {
// ...
}
表达式
#[cfg()]的一个值得注意的应用是编译时覆盖函数。通过定义具有不同条件的函数的多个实现,以适应不同的目标平台或编译配置。这种灵活性确保只编译和执行相关的代码路径,从而生成更高效的二进制文件。
// 确定Windows的缓存目录
#[cfg(target_os = "windows")]
const fn default_cache_dir() -> &'static str {
"C:/cache"
}
// 确定Linux的缓存目录
#[cfg(target_os = "linux")]
const fn default_cache_dir() -> &'static str {
"/mnt/c/cache"
}
然后在其他地方这样调用
generate_cache_items_from(default_cache_dir());
Rust确保任何未使用的变量或不支持的函数调用都被检测和适当处理。例如,如果一个函数只在Linux目标上运行时可用,那么调用该函数也必须被标记为条件以匹配。但是如果有处理其他目标的函数,那么在编译时将知道调用哪个函数。
为了继续我们前面的例子,尝试在Windows或Linux上编译我们的程序将运行良好。如果我们想让程序在MacOS上编译,我们需要显式地声明语句以支持编译的目标,如下所示:
#[cfg(any(target_os = "windows", target_os = "linux"))]
generate_cache_items_from(default_cache_dir());
#[cfg(target_os = "macos")]
println!("Caching is not supported on MacOS");
此属性不限于特定的代码构造。它可以应用于语句、函数、块、导入语句等。这种多功能性允许开发人员根据他们的特定需求,在不同粒度级别上选择性地包含或排除代码。
Modules
这可能是你以前见过的!
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_and_get() {
// ...
}
}
代码块
#[cfg(not(vendor_testing))]
{
trace!("Starting payment processing");
if let Ok(details) = process_payment(&transaction) {
db.store(&details);
}
trace!("Ending payment processing");
}
#[cfg(vendor_testing)]
{
debug!("Starting mock payment processing");
if let Ok(details) = mock_process_payment(&transaction) {
debug!("Payment details", &details);
}
debug!("Ending mock payment processing");
}
枚举变量
#[non_exhaustive]
pub enum Tab {
Dashboard,
Settings,
#[cfg(feature = "admin")]
Admin,
}
向量中的元素
impl Tab {
pub fn all() -> Vec<Self> {
vec![
Self::Dashboard,
Self::Settings,
#[cfg(feature = "admin")]
Self::Admin,
]
}
}
结构体
#[cfg(debug_assertions)]
#[derive(Default)]
pub struct DevBuildState {
dev_id: Mutex<Option<String>>,
}
导入语句
#[cfg(debug_assertions)]
use crate::state::DevBuildState;
Cargo
[target."cfg(not(target_os = "macos"))".dependencies]
some-crate = "0.53.1"
[target."cfg(target_os = "macos")".dependencies]
some-crate-macos-patch = { git = "https://github.com/some-org/some-crate", branch = "macos-patch" }
相关特性
宏操作
除了#[cfg()]属性,Rust还提供了一个名为cfg!()的宏版本。该宏可用于根据编译时条件执行内联if语句。该宏不会有条件地包含/排除代码块。在编译时仍然会检查和替换条件,但实际上不会删除任何代码。
window.set_title(if cfg!(feature = "admin") && user.is_admin {
"Admin User Settings"
} else {
"User Settings"
});
属性扩展
这个#[cfg_attr()]属性允许开发人员根据谓词有条件地包含其他属性,如下所示:
#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}
// 当启用' magic '特性标志时,上述内容将扩展为
#[sparkles]
#[crackles]
fn bewitched() {}
习惯用法
如果在运行程序时提供了额外的--cfg选项:
rustc --cfg "foobar" main.rs
或者通过在构建脚本中指示Cargo:
println!("cargo:rustc-cfg=foobar");
然后,你可以通过以下方式访问它:
#[cfg(foobar)]
总结
Rust的#[cfg()]属性是一个强大的工具,可以在编译时根据指定的条件有条件地编译代码。它可以对代码的哪些部分包含或排除进行细粒度控制,从而产生更高效和优化的程序!通过混合使用条件和组合,开发人员可以根据不同的平台、特性和编译配置定制他们的代码库。