---
title: 'Dependencies and Feature Flags'
source: 'https://academia.sh/en/courses/rust-advanced/dependencies-and-feature-flags'
course: 'The Rust Ecosystem and Advanced Topics'
language: en
updated: '2026-09-15T04:58:43+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Runs code to show that a body excluded by conditional compilation is never seen by the compiler'
    - 'Repeats the same observation with two separate feature flags to confirm the class (nobody checks it) is not a coincidence'
    - "Explains that this measurement does not show what happens once the flag is turned on, only proving today's state"
---

# Dependencies and Feature Flags

That a piece of source excluded by conditional compilation is never seen by the compiler at all, confirmed by repeating the same observation with two separate feature flags to establish the class (nobody checks it).

The Low-Level and Interoperability topic closed with four lessons, and in all four the
compiler still saw at least something: a few operations inside an `unsafe` block stayed
unchecked, but another operation in the same block was still checked; a foreign function's
body was never seen, but at least its signature entered compilation; the limit a target
imposes was read from the source, and two versions — one relying on the standard library,
one not — ran side by side on the native target. In all four, the compiler compiled *some
part* of the source, and the guarantee started from there — the checking narrowed, but it
never stopped entirely. The Project Lifecycle topic asks a completely different question: is
there a piece that sits in the source but that the compiler never sees at all, and if so,
does anyone notice even if that piece breaks?

This lesson looks for the answer in conditional compilation. A section of a source file
enters compilation or does not, depending on whether a feature flag is on; a section that
does not enter passes through no checking at all, beyond its syntax merely having to be
valid. The Rust Ecosystem and Advanced Topics course's shared question — where does the
compiler's guarantee end here — finds its sharpest answer in this lesson: for some bodies,
the guarantee never starts at all.

The course's four answers — the guarantee **still holding**, being **handed to the
writer**, falling **outside the language**, or ending up where **nobody checks it** — each
find their counterpart, one by one, in the Project Lifecycle topic's four lessons: this
lesson shows a body that is never checked at all, the next two show which guarantee a test
or a count establishes *by running*, and the last lesson shows that the same source can hold
two claims side by side — one never checked, one genuinely checked.

## The Body Left Out of Scope

A manifest file normally declares, through feature flags, which parts of a crate are
optional; the package manager passes these flags to the compiler with the `--cfg` option.
Since this course does not use a package manager, the flag state is given directly to the
compiler, but the mechanism is identical: an item marked with the `#[cfg(feature = "name")]`
attribute enters compilation only while that flag is on. While the flag is off, the compiler
**never sees** that item at all — it neither runs it through type checking nor resolves its
names.

Dependencies and feature flags are usually mentioned together because a flag is frequently
used to limit the need for a dependency: if a section relies on a particular piece of
external functionality that most users do not need, that section is placed behind a flag,
and while the flag is off, neither the section nor the dependency it relies on ever enters
compilation. This lesson does not use a dependency outside the standard library —
dependencies stay limited to the standard library throughout the course — but the mechanism
is identical: `#[cfg(...)]` leaves an item out of compilation regardless of what it depends
on.

Feature flags are mostly used to separate a crate's optional sections: a section that most
users do not need is placed behind a flag, and only the party that wants it turns that flag
on. A manifest file also states which of these flags come on **by default**; unless
explicitly turned off, default flags enter every build, and the rest are turned on only when
the requesting party reaches for them. The payoff of this is that the unused section is
never compiled at all — but what this lesson measures is not the payoff, it is this payoff's
**cost**: the section that stays off lives at a different level of checking than the section
that stays on.

The section below carries two bodies marked with two flags. The first sits under the
`unopened` flag and carries two different errors: it calls a function that does not exist
and assigns a string directly to an integer. The second sits under the `untried` flag and,
in a similar way, calls a method that does not exist and assigns its result to the wrong
type. Both bodies were deliberately built to violate two different kinds of static checking:
one violates **name resolution** (a call that does not exist), the other violates **type
checking** (a wrong assignment). Both were needed to show that they arrive at the same
outcome — silently staying out — because choosing only one kind of error would have let the
observation apply to only that error class. The third body is not tied to any flag and
enters compilation under every condition.

```rust
// flag_bodies.rs — two separate flags that always stay off, one body that always stays on
#[cfg(feature = "unopened")]
pub fn never_checked() {
    this_function_was_never_defined();
    let _x: i32 = "text";
}

#[cfg(feature = "untried")]
pub fn another_unchecked(list: &[i32]) -> i32 {
    let total: i32 = list.parallel_sum();
    let _s: &str = total;
    total
}

pub fn running_body() -> i32 {
    41
}
```

This file carries no `fn main` at all; it stands as a source section, and the body below
pulls it in with a `mod` declaration. **PL1.** The claim here is this: when no flag is on,
compilation finishes **without warnings** even though the two faulty bodies inside
`flag_bodies.rs` remain in the source — because neither one ever enters compilation.

```rust
// flag_measurement.rs — both flags off; the two excluded bodies are never checked at all
mod flag_bodies;
use flag_bodies::running_body;

fn main() {
    println!("running body result: {}", running_body());
    println!("is the unopened flag on: {}", cfg!(feature = "unopened"));
    println!("is the untried flag on: {}", cfg!(feature = "untried"));
}
```

```
running body result: 41
is the unopened flag on: false
is the untried flag on: false
```

Compilation printed no warning at all, and the program wrote three lines. Both
`never_checked` and `another_unchecked` remain in the source, each carrying an undefined
call and a type error, and neither was **compiled at all** in this run. Because the compiler
never saw them, it could catch neither the call nor the faulty assignment. A runtime flag
query inside the code confirms this from within the program: both flags read as off during
the run — both off, both bodiless.

This carries an important distinction for not confusing a flag with an ordinary runtime
condition. If the same two faulty bodies had been written inside an ordinary runtime branch
instead of `#[cfg(...)]` — say, a condition that is never true — the outcome would not be
the same: even if such a branch's body never runs at runtime, it still **passes type
checking** at compile time, because it remains present in the source tree. If an undefined
call were written there, compilation would fail — a branch that is never reached at runtime
does not hide an undefined name. `#[cfg(...)]` does exactly the opposite: it removes the
body from the source tree entirely, and with it from both the name-resolution and
type-checking stages. **PL2.** This difference shows that "code that never runs" and "code
that is never compiled" are not the same thing; only the latter falls into this lesson's
class, the nobody-checks-it class.

**Distinguishing this from a related idea:** the Production Go: Testing, Tooling and
Performance course measured build tags as a program's **tool-level knowledge** — a build tag
decided which file compiled for which operating system or architecture, and it was a tool
that reported this. What this lesson measures is entirely different: that the excluded body
is **never checked at all**. There, the question was whether a piece of knowledge came from
the source, a tool, or a run; here, the question is whether a part of the source ever enters
the compiler's sphere of interest at all. Both ideas use the `#[cfg(...)]` attribute — in
Rust, target selection and feature selection both go through the same mechanism — but they
ask different questions: one asks "which file compiles for where," the other asks "who
checks a body that is never compiled."

The same observation was repeated with a second flag (`untried`) because a single flag
staying off could have been a coincidence — maybe that flag was designed never to be
used at all. **PL3.** Two different flags, carrying two different kinds of error (an
undefined call with a wrong type assignment, then an undefined method with a wrong type
assignment), reached the same outcome: a closed flag's body, whatever it contains, never
enters the compilation process at all. This is not a special case of a single example; it is
conditional compilation's general behavior.

A project can reduce this risk by delegating it to a process that compiles every flag
combination separately: if every combination is compiled at least once, no flag stays off at
that moment, and checking momentarily reaches full coverage. This does not remove the
measured class from **nobody-checks-it** — it only defers the moment of checking to the
moment the flag is turned on. Every moment such a process is not run, the bodies behind the
source tree's closed flags keep staying unchecked; what this lesson measures is what happens
in that process's **absence**, not what is guaranteed in its presence.

## A Bounding Measurement

The run above showed that a closed flag's body does not enter compilation. But that does not
mean the body is *invisible* — it sits in the source, it can be read, it can be tracked in
the version control system. What is invisible is whether the compiler looks at it. The run
below shows this distinction from within a running program: a block set aside for a closed
flag's body is never executed by the program itself, either.

```rust
// body_visibility.rs — a closed flag's body is invisible from the run, too
mod flag_bodies;
use flag_bodies::running_body;

fn main() {
    let result = running_body();
    #[cfg(feature = "unopened")]
    {
        println!("this line never compiles");
    }
    #[cfg(not(feature = "unopened"))]
    {
        println!("unopened flag is off: its body stays in the source, never entered the run");
    }
    println!("running body returned {} again", result);
}
```

```
unopened flag is off: its body stays in the source, never entered the run
running body returned 41 again
```

**PL4.** Both blocks are written inside the same `main` function, but only one enters the
run — which one enters is decided at compile time, by looking at the `cfg` attribute. Both
blocks exist in the source; only one exists in the run. This is not a promise conditional
compilation makes to the reader, it is a fact that can be derived from the source itself —
but this measurement itself carries a limit.

In this run, `#[cfg(...)]` sits not on an item (a function) but on an expression block
inside `main`; the attribute itself can apply to functions, to structs, and to a single
expression block alike. In a real project this means that only a small part of a function —
say, a logging call or an extra check — can be placed behind a flag while most of the
function stays shared; tying the entire function to the flag is not required. The measured
class does not change: whatever level it is applied at, a block that stays off still never
enters compilation at all.

In the run above, `#[cfg(feature = "unopened")]` and `#[cfg(not(feature = "unopened"))]`
were written **at the same spot**, as two mutually exclusive blocks. This is another common
form flags take: one behavior runs while a flag is on, its opposite runs while the flag is
off, and both stay visible in the source. The compiler only compiles whichever of the two
matches the flag's state, and leaves the other out entirely, just like `never_checked` above
— the side that stays off is not faulty here, merely off; but the fact that the compiler
never looks at it does not change.

This measurement shows that a closed flag's body is not compiled *today*; it does not show
whether the body **would or would not compile** once the flag is turned on. The only way to
show that is to actually turn the flag on and try compiling, but `never_checked` and
`another_unchecked` are deliberately built so that they would not compile once turned on —
running a source that does not compile is not possible in this lesson. So the measurement
only proves this: **today, nobody checks a closed flag's body.**

Comparing this with an ordinary unused item makes the distinction clearer. If a function is
written tied to no flag at all but is never called, the compiler notices and prints an
unused warning — because that function did enter the compilation process, had its name
resolved and its type checked, and was never called. A closed flag's body never gets
this warning, because it never entered the compilation process at all; the reason for the
silence is not "known to be unused and ignored," it is "treated as if it had never existed."
Because every code block delivered in this lesson compiles without warnings, this comparison
is not run here, but the difference between the warning the compiler gives unused code and
the warning it does (not) give a closed flag's body explains why the nobody-checks-it class
belongs to closed flags specifically.

What happens once it is turned on is not known until it is turned on — and this unknown is
the direct reason a closed flag's body loses sync (rots) with the rest of the source over
time. As the source tree grows, as the types and function signatures around it change, a
closed flag's body never has to keep up with any of these changes; because the compiler
never checks it, the mismatch only surfaces on the day the flag is turned on.

This limit does not end with a single flag, either. Even if a source tree carries only two
independent flags, the on/off pair produces four possible compilation states; as the number
of flags grows, the number of possible states grows exponentially. An ordinary build
pipeline actually compiles only a few of these states — usually the extremes, where
everything is off or everything is on — and most of the combinations in between are never
tried. The run above showed one of these extremes by leaving both flags off in a single
source file; a state where `unopened` and `untried` are turned on **together** was never
built in this lesson, because such a state would bring both bodies into compilation, and
both are deliberately built so that neither would compile. As the number of untried
combinations grows, predicting when a closed flag will be turned on and what happens at that
moment also grows harder. This lesson showed only two flags, and only the most extreme of
the four possible combinations (both off); the remaining three combinations — only
`unopened` on, only `untried` on, both on — stayed outside this measurement's scope, and
that very exclusion is itself another instance of the measured class (nobody-checks-it).

## Summary

- An item marked with `#[cfg(feature = "name")]` never enters the compiler while that flag
  is off; compilation finishes without warnings even if the item carries an undefined call
  and a type error.
- This observation was repeated with two different flags (`unopened`, `untried`) and two
  different kinds of error; the outcome stayed the same in both.
- The guarantee's class is **nobody checks it**: neither the compiler, nor a tool, nor the
  running program checks a closed flag's body.
- An ordinary runtime branch (like `if`) only removes the body from the execution path, and
  the body still passes type checking; `#[cfg(...)]` removes the body from the source tree,
  and type checking never even starts — the two forms of exclusion are not the same thing.
- This measurement's limit: it shows that a closed flag's body is not compiled today, but it
  does not show whether it would compile once turned on — that is known only once the flag
  is genuinely turned on.
- A body behind a flag that stays off cannot keep up with changes in the source over time;
  noticing this is left to the moment the flag is turned on.

## Next Step

This lesson showed how a body nobody looks at can sit silently; the reason for that silence
was not a hidden error, it was that the body never entered the compilation process at all.
The next lesson asks the opposite question: a body enters compilation, passes type checking,
and can still run wrong — why is the compiler's checking not enough to say a body **runs
correctly**, and how much can a test that runs to close this gap be trusted? The Testing and
Property-Based Testing lesson answers this question, one the compiler never asks, not with a
closed flag this time but with a property tested by running.
