#variables #macro #cfg

no-std cfgenius

Conditional compilation with macro support in Rust

2 releases

new 0.1.1 Apr 22, 2024
0.1.0 May 28, 2023

#373 in Rust patterns

Download history 5/week @ 2024-01-04 11/week @ 2024-01-11 4/week @ 2024-01-18 1/week @ 2024-02-01 6/week @ 2024-02-08 21/week @ 2024-02-15 29/week @ 2024-02-22 26/week @ 2024-02-29 35/week @ 2024-03-07 22/week @ 2024-03-14 37/week @ 2024-03-21 48/week @ 2024-03-28 32/week @ 2024-04-04 28/week @ 2024-04-11 168/week @ 2024-04-18

281 downloads per month
Used in 3 crates (via jit-allocator)

MIT license

20KB
233 lines

CFGenius

A macro for defining #[cfg] if-else statements containing potentially cross-crate variables.

This macro is similar to cfg_if!—so similar, in fact, that we're going to plagiarize its documentation for a bit:

The macro provided by this crate, cond!, is similar to the if/elif C preprocessor macro by allowing definition of a cascade of #[cfg] cases, emitting the implementation which matches first.

This allows you to conveniently provide a long list #[cfg]'d blocks of code without having to rewrite each clause multiple times.

Example

cfgenius::cond! {
    if cfg(unix) {
        fn foo() { /* unix specific functionality */ }
    } else if cfg(target_pointer_width = "32") {
        fn foo() { /* non-unix, 32-bit functionality */ }
    } else {
        fn foo() { /* fallback implementation */ }
    }
}

What's new, however, is the ability to define! custom conditional-compilation variables and use those variables in your cond! predicates:

// In `crate_1`...
cfgenius::define! {
    pub(super) is_32_bit_or_more = cfg(any(
        target_pointer_width = "32",
        target_pointer_width = "64",
    ));

    pub is_recommended = all(
        macro(is_32_bit_or_more),
        macro(is_supported),
        cfg(target_has_atomic),
    );
}

cfgenius::cond! {
    if all(cfg(windows), not(macro(is_32_bit_or_more))) {
        cfgenius::define!(pub is_supported = true());

        // windows-specific non-32-bit functionality
    } else if all(cfg(windows), macro(is_32_bit_or_more)) {
        cfgenius::define!(pub is_supported = true());

        // windows-specific non-32-bit functionality
    } else {
        cfgenius::define!(pub is_supported = false());
    }
}

pub const IS_SUPPORTED: bool = cfgenius::cond_expr!(macro(is_supported));

// In `crate_2`...
cfgenius::cond! {
    if any(
        macro(crate_1::is_recommended),
        all(cfg(feature = "force_crate_1_backend"), macro(crate_1::is_supported))
    ) {
        // (`crate_1` implementation)
    } else {
        // (fallback implementation)
    }
}

This is not possible in regular #[cfg] attributes:

macro_rules! truthy {
    () => { all() };
}

#[cfg(truthy!())]
//          ^ Syntax Error: expected one of `(`, `,`, `::`, or `=`, found `!`
mod this_is_compiled {}

Predicates

In every place where we could expect a conditionally compiled predicate, the following predicates are supported:

  • true(): is always truthy

  • false(): is always falsy

  • cfg(<cfg input>): resolves to the result of a regular cfg attribute with the same input.

  • not(<predicate>): negates the resolution of the provided cfgenius predicate.

  • all(<predicate 1>, <predicate 2>, ...): resolves to truthy if none of the provided cfgenius predicates fail. all() with no provided predicates resolves to true.

  • any(<predicate 1>, <predicate 2>, ...): resolves to truthy if at least of the provided cfgenius predicates succeed. any() with no provided predicates resolves to false.

  • macro(<path to macro>): uses the macro to determine the truthiness of the predicate.

  • macro(<path to macro> => <macro arguments>): uses the macro with the provided arguments to determine the truthiness of the predicate.

Custom Variables

Most variables can be succinctly defined using define!. However, because variables are just macros which are expanded to get their result, you can define your own variables by following this protocol.

The predicate macro(<path to macro>) is evaluated by expanding:

path::to::macro! {
    yes { /* truthy tokens */ }
    no { /* falsy tokens */ }
}

...and the predicate macro(<path to macro> => <macro arguments>) is evaluated by expanding:

path::to::macro! {
    args { /* macro arguments */ }
    yes { /* truthy tokens */ }
    no { /* falsy tokens */ }
}

If the variable should be truthy, the macro should expand to /* truthy tokens */ and nothing more. If the variable should be falsy, the macro should expand to /* falsy tokens */ and nothing more.

These macros should be effectless and pure with respect to their environment. You should not rely on this macro being evaluated once for every time it appears in a predicate, even though this is the current behavior.

No runtime deps