Skip to content

← Modules, Visibility, Testing and Docs step 12 of 22

Medium Framework

Conditional compilation with cfg and cfg_select!

Conditional compilation lets a Rust program include code only when a build condition holds. Practice the distinction between selecting code and testing a boolean by implementing these two functions:

pub fn eval_cfg(predicate: &str, active: Vec<String>) -> Result<bool, String>
pub fn pointer_class() -> &'static str

Evaluate a small predicate language

eval_cfg evaluates a predicate against the supplied active options, independently of the compiler's own build settings.

Form Meaning
unix Is this bare option active?
target_os="linux" Is this exact key-value option active?
all(p, q) Are all child predicates true?
any(p, q) Is at least one child predicate true?
not(p) Invert one child predicate
true, false Boolean literals, regardless of active

Calls can nest. all() is true and any() is false. A single trailing comma is accepted after an argument, as in all(unix,); leading commas and missing arguments between commas are invalid. not takes exactly one argument.

For this exercise, remove every character recognized by char::is_whitespace from both the predicate and each active option before parsing or comparison. This includes whitespace inside quoted values: label="a b" becomes label="ab". Duplicate active options have no effect, and several values for one key can be active at once.

Names use ASCII letters or _ first, followed by ASCII letters, digits or _. A value is enclosed in double quotes and can contain any non-quote characters, including commas and parentheses. Those characters inside a value are data, not argument separators or grouping syntax. Backslashes are literal characters; this grammar does not decode escape sequences or support raw strings. Assume nesting and input sizes fit available resources.

These are deliberate simplifications. Real Rust preserves whitespace inside string values and supports Rust string literals and identifiers. This parser is an exercise in recursive structure, not a replacement for the compiler.

Report invalid input

Check an empty normalized input first, then check parentheses outside quoted values. Evaluate child predicates from left to right, checking every child even if the boolean result is already known. For example, any(true, 7) is an error, not Ok(true).

Situation Exact error string
Empty normalized input empty predicate
Unbalanced parentheses outside values unbalanced parentheses
not has zero or more than one argument not() takes exactly one predicate
Invalid leaf or balanced expression bad predicate: <text>

In the last row, <text> is the normalized expression being evaluated. An empty argument produces bad predicate: with a space after the colon. Check not's argument count before evaluating its children. Active options are normalized strings used for membership checks; do not recursively evaluate them or reject the whole request because one is malformed.

Select code with cfg, test a boolean with cfg!

#[cfg(...)] excludes the annotated item when its predicate is false. Excluded function bodies do not need to resolve names or type-check, but ordinary Rust source must still parse. A missing type in an excluded function is different from a missing closing delimiter.

cfg!(...) produces a boolean. It does not remove either branch of an if, so both branches must resolve and type-check for the build target:

#[cfg(any())]
fn excluded() -> MissingType {
    missing_function()
}

fn main() {
    let family = if cfg!(unix) { "unix" } else { "other" };
    assert!(matches!(family, "unix" | "other"));
}

cfg_attr conditionally applies another attribute. For example, this type gets a Debug implementation in builds with debug assertions enabled:

#[cfg_attr(debug_assertions, derive(Debug))]
struct Reading(i32);

fn main() {
    let reading = Reading(7);
    #[cfg(debug_assertions)]
    assert_eq!(format!("{reading:?}"), "Reading(7)");
    assert_eq!(reading.0, 7);
}

Select the first matching arm

cfg_select!, available since Rust 1.95, selects the first true predicate and emits its code. It can produce items or an expression. Use it for pointer_class, returning "64-bit", "32-bit", "16-bit" or "unknown" according to target_pointer_width. The harness checks the result against size_of::<usize>() on the machine compiling the submission.

fn main() {
    let bits = cfg_select! {
        target_pointer_width = "64" => 64,
        target_pointer_width = "32" => 32,
        target_pointer_width = "16" => 16,
        _ => 0,
    };
    assert_eq!(bits, match size_of::<usize>() {
        2 => 16,
        4 => 32,
        8 => 64,
        _ => 0,
    });
}

Put overlapping cases in the order you intend them to take precedence. The starter puts _ first, making later arms unreachable; move the fallback last. The compiler's unreachable_cfg_select_predicates lint reports this, and the warnings-as-errors grading gate rejects it. If no predicate matches and there is no fallback, the macro emits a compilation error.

Compiler options affect the real cfg set. The current runner builds an optimized executable without --test; debug assertions are off by default there. Its separate Clippy check does not use the same optimization flag, so do not rely on both passes having identical cfg settings. That has no effect on eval_cfg: if its supplied options include test or debug_assertions, those options are active for that evaluation.

Clippy can flag redundant expressions such as all(unix) with non_minimal_cfg. The compiler can catch unexpected cfg names or values with unexpected_cfgs when cfg checking is enabled, for example through Cargo or rustc's --check-cfg. Do not assume a bare rustc invocation checks every misspelling. Submissions must compile, pass the tests and pass Clippy with warnings treated as errors.