Rust's cfg Attribute

Posted on 6/1/2023

How it works

Rust’s #[cfg()] attribute is a powerful feature that allows developers to conditionally compile code based on specified conditions at compile time. It enables fine-grained control over which portions of code are included or excluded during the compilation process, resulting in more efficient and optimized programs.

At its core, #[cfg()] is a compile-time attribute macro (as opposed to a function-like macro). It allows Rust to evaluate the predicate and decide whether to include or exclude specific code blocks or items during compilation. We’ll walk through many examples of how to express the predicate as well as where this attribute can be used.

NOTE: cfg is short for configuration! You can read it as “if this configuration exists, include this code”.


Conditions

Rust provides several built-in options that can be used with #[cfg()] to define what conditions should allow the code to be included in the build.

Target Operating System

Docs

#[cfg(target_os = "android")]
fn connect_to_instant_apps() {
    // ...
}

Target Arch

Docs

#[cfg(target_arch = "x86_64")]
fn target_system_config() -> Option<SystemConfig> {
    // ...
}

Feature flags

With the feature option, developers can conditionally compile code based on whether a specific feature flag is enabled. This is particularly useful when building libraries or applications with optional features.

#[cfg(feature = "server")]
fn render(nodes: Vec<Node>) -> impl View {
    // ...
}

Debug Mode

Docs

The debug_assertions option allows you to selectively compile code for debugging purposes.

#[cfg(debug_assertions)]
fn debug_payload(payload: &Payload) {
    // Code runs only in debug mode
}

Tests

Docs

#[cfg(test)]
fn save(&self) {
    // Don't persist since this isn't production code
}

Combinators

#[cfg()] has the ability to handle complex conditions using various combinators while still remaining readable.

All conditions

For instance, the all() combinator allows multiple conditions to be combined using logical AND, ensuring that all conditions must evaluate to true for the associated code to be compiled.

// Only included when compiling for a unixish OS with a 32-bit architecture
#[cfg(all(unix, target_pointer_width = "32"))]
fn on_32bit_unix() {
    // ...
}

Any conditions

On the other hand, the any() combinator uses logical OR, allowing the code to be compiled if any of the specified conditions are true.

// This will compile when targetting Android or iOS
#[cfg(any(target_os = "android", target_os = "ios"))]
fn on_mobile() {
    // ...
}

Negate condition(s)

Additionally, the not() combinator negates the condition (or set of conditions), allowing the code to be compiled when the specified condition is false. Here’s the opposite of the previous function:

// This will compile when NOT targetting Android or iOS
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn not_on_mobile() {
    // ...
}

Expressiveness

A notable application of #[cfg()] is compile-time function overriding. By defining multiple implementations of a function with different conditions, Rust allows for clean and concise code that adapts to different target platforms or compilation configurations. This flexibility ensures that only the relevant code paths are compiled and executed, leading to more efficient binaries.

// Determine cache dir for Windows.
#[cfg(target_os = "windows")]
const fn default_cache_dir() -> &'static str {
    "C:/cache"
}

/// Determine cache dir for Linux.
#[cfg(target_os = "linux")]
const fn default_cache_dir() -> &'static str {
    "/mnt/c/cache"
}

And then somewhere else:

generate_cache_items_from(default_cache_dir());

Rust ensures that any unused variables or unsupported function invocations are detected and handled appropriately. For example, if a function is only available when running on Linux targets, then calling that function must also be marked as conditional to match. But if there are functions to handle the other targets then at compile time it will know which function will be called.

To continue our previous example, trying to compile our program on Windows or Linux will work fine. We’ll need to explicitly state which combination of targets that statement supports if we want our program to compile on MacOS though, like so:

#[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");

This attribute is not limited to specific code constructs. It can be applied to statements, functions, blocks, imports, and more. This versatility allows developers to selectively include or exclude code at various levels of granularity, depending on their specific requirements. Here are several examples:

Modules

This is one you’ve probably seen before!

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn insert_and_get() {
        // ...
    }
}

Code blocks

// The real deal
#[cfg(not(vendor_testing))]
{
    trace!("Starting payment processing");
    if let Ok(details) = process_payment(&transaction) {
        db.store(&details);
    }
    trace!("Ending payment processing");
}
// When testing vendor payments
#[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");
}

Enum variants

// This should be marked as non_exhaustive for a better public DX
#[non_exhaustive]
pub enum Tab {
    Dashboard,
    Settings,
    #[cfg(feature = "admin")]
    Admin,
}

Vec items

impl Tab {
    pub fn all() -> Vec<Self> {
        vec![
            Self::Dashboard,
            Self::Settings,
            #[cfg(feature = "admin")]
            Self::Admin,
        ]
    }
}

Structs

// In src/state.rs
#[cfg(debug_assertions)]
#[derive(Default)]
pub struct DevBuildState {
  dev_id: Mutex<Option<String>>,
}

Import statements

// In another file:
#[cfg(debug_assertions)]
use crate::state::DevBuildState;

Cargo deps

[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" }

Macro option

In addition to the #[cfg()] attribute, Rust also provides a macro version called cfg!(). This macro can be used to perform inline if-statements based on compile-time conditions.

This macro does not conditionally include/exclude the code blocks. The condition will still be checked and replaced at compile-time, but no code is actually removed.

window.set_title(if cfg!(feature = "admin") && user.is_admin {
    "Admin User Settings"
} else {
    "User Settings"
});

Attribute expansion

This #[cfg_attr()] attribute allows developers to conditionally include other attributes based on a predicate, as shown below:

#[cfg_attr(feature = "magic", sparkles, crackles)]
fn bewitched() {}

// When the `magic` feature flag is enabled, the above will expand to:
#[sparkles]
#[crackles]
fn bewitched() {}

Custom flags

If additional --cfg options are supplied when running the program:

rustc --cfg "foobar" main.rs

or by instructing Cargo in a build script:

println!("cargo:rustc-cfg=foobar");

Then it will be accessible to you via:

#[cfg(foobar)]

See it in action

Tauri, a wonderful framework for making applications, is using it for configuring the system tray:

#[non_exhaustive]
pub struct SystemTray {
    /// The tray identifier. Defaults to a random string.
    pub id: String,
    /// The tray icon.
    pub icon: Option<tauri_runtime::Icon>,
    /// The tray menu.
    pub menu: Option<SystemTrayMenu>,
    /// Whether the icon is a template icon or not.
    #[cfg(target_os = "macos")]
    pub icon_as_template: bool,
    /// Whether the menu should appear when the tray receives a left click.
    /// Defaults to `true`
    #[cfg(target_os = "macos")]
    pub menu_on_left_click: bool,
    on_event: Option<Arc<TrayEventHandler>>,
    #[cfg(target_os = "macos")]
    menu_on_left_click_set: bool,
    #[cfg(target_os = "macos")]
    icon_as_template_set: bool,
    #[cfg(target_os = "macos")]
    title: Option<String>,
    tooltip: Option<String>,
}

Here are more examples of open source projects using this feature. Enjoy!


Summary

Rust’s #[cfg()] attribute is a powerful tool for conditionally compiling code based on specified conditions at compile time. It enables fine-grained control over which portions of code are included or excluded, resulting in more efficient and optimized programs! By utilizing a mix of conditions and combinators, developers can tailor their codebase to different platforms, features, and compilation configurations.

© 2024 Parker McMullin

𝕏 GitHub