changed a year ago
Published Linked with GitHub

--check-cfg Stabilization Report
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Summary

This adds a new top-level command line option --check-cfg (to rustc and rustdoc) in order to enable checking conditional compilation at compile time.

rustc will check every #[cfg(name = "value")], #[cfg_attr(name = "value")], #[link(name = "a", cfg(name = "value"))] attributes and cfg!(name = "value") macro calls. --cfg arguments are not checked[1].

The syntax looks (roughly) like this:

rustc --check-cfg 'cfg(name, values("value1", "value2", ... "valueN"))'

From the unstable book documentation:

To enable checking of values, but to provide an none/empty set of expected values
(ie. expect #[cfg(name)]), use these forms:

rustc --check-cfg 'cfg(name)'
rustc --check-cfg 'cfg(name, values(none()))'

To enable checking of name but not values, use one of these forms:

  • No expected values (will lint on every value):

    ​​​​rustc --check-cfg 'cfg(name, values())'
    
  • Unknown expected values (will never lint):

    ​​​​rustc --check-cfg 'cfg(name, values(any()))'
    

To avoid repeating the same set of values, use this form:

rustc --check-cfg 'cfg(name1, ..., nameN, values("value1", "value2", ..., "valueN"))'

The --check-cfg cfg(...) option can be repeated, both for the same condition name and for different names. If it is repeated for the same condition name, then the sets of values for that condition are merged together (precedence is given to values(any())).

Like with values(any()), well known names checking can be disabled by passing cfg(any()) as argument to --check-cfg.

Example

rustc --check-cfg 'cfg(is_embedded, has_feathers)' \
      --check-cfg 'cfg(feature, values("zapping", "lasers"))' foo.rs
#[cfg(is_embedded)]         // This condition is expected, as 'is_embedded' was provided in --check-cfg
fn do_embedded() {}         // and doesn't take any value

#[cfg(has_feathers)]        // This condition is expected, as 'has_feathers' was provided in --check-cfg
fn do_features() {}         // and doesn't take any value

#[cfg(has_mumble_frotz)]    // This condition is UNEXPECTED, as 'has_mumble_frotz' was NEVER provided
                            // in any --check-cfg arguments
fn do_mumble_frotz() {}

#[cfg(feature = "lasers")]  // This condition is expected, as "lasers" is an expected value of `feature`
fn shoot_lasers() {}

#[cfg(feature = "monkeys")] // This condition is UNEXPECTED, as "monkeys" is NOT an expected value of
                            // `feature`
fn write_shakespeare() {}
warning: unexpected `cfg` condition name: `has_mumble_frotz`
 --> foo.rs:7:7
  |
7 | #[cfg(has_mumble_frotz)]
  |       ^^^^^^^^^^^^^^^^
  |
  = help: expected names are: `debug_assertions`, `doc`, `doctest`, `feature`, `has_feathers`, `is_embedded`, `miri`, `overflow_checks`, `panic`, `proc_macro`, `relocation_model`, `sanitize`, `sanitizer_cfi_generalize_pointers`, `sanitizer_cfi_normalize_integers`, `target_abi`, `target_arch`, `target_endian`, `target_env`, `target_family`, `target_feature`, `target_has_atomic`, `target_has_atomic_equal_alignment`, `target_has_atomic_load_store`, `target_os`, `target_pointer_width`, `target_thread_local`, `target_vendor`, `test`, `unix`, `windows`
  = help: to expect this configuration use `--check-cfg=cfg(has_mumble_frotz)`
  = note: see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration
  = note: `#[warn(unexpected_cfgs)]` on by default

warning: unexpected `cfg` condition value: `monkeys`
  --> foo.rs:14:7
   |
14 | #[cfg(feature = "monkeys")]
   |       ^^^^^^^^^^^^^^^^^^^
   |
   = note: expected values for `feature` are: `lasers`, `zapping`
   = help: to expect this configuration use `--check-cfg=cfg(feature, values("monkeys"))`
   = note: see <https://doc.rust-lang.org/nightly/unstable-book/compiler-flags/check-cfg.html> for more information about checking conditional configuration

warning: 2 warnings emitted

Well known names and values

From the unstable book section:

rustc has a internal list of well known names and their corresponding values.
Those well known names and values follows the same stability as what they refer to.

As of 2024-01-09T, the list of known names is as follows:

  • debug_assertions
  • doc
  • doctest
  • miri
  • overflow_checks
  • panic
  • proc_macro
  • relocation_model
  • sanitize
  • sanitizer_cfi_generalize_pointers
  • sanitizer_cfi_normalize_integers
  • target_abi
  • target_arch
  • target_endian
  • target_env
  • target_family
  • target_feature
  • target_has_atomic
  • target_has_atomic_equal_alignment
  • target_has_atomic_load_store
  • target_os
  • target_pointer_width
  • target_thread_local
  • target_vendor
  • test
  • unix
  • windows

Cargo integration

Since ~1.61 nightly Cargo there has been some form of integration of --check-cfg in Cargo with the -Zcheck-cfg.

The latest and current one is -Zcheck-cfg which:

Enables checking conditional compilation at compile time with rustc --check-cfg. It enables feature, well known names, well known values and cargo::rustc-check-cfg (in build scripts).

A stabilization report for Cargo will be done and would propose to make the behavior of -Zcheck-cfg the default.

Documentation and Testing

The feature is described in the unstable book under it's own section.

The feature is being extensively tested under tests/ui/check-cfg (27 test files). To give a some highlights:

Real World Usage

Unresolved questions

  • From the tracking issue: Which configs should be in the well known list?
    Currently the informal logic that has been followed is to include all the configs that are:

    1. User facing (nightly or not) (rational: internal cfgs are by their name not to be shown)
    2. Set by a Rust Toolchain tool (rational: rustc is not the only tool that sets cfgs, rustdoc sets doc and doctest, cargo-miri sets miri, docs.rs sets docsrs, )
    3. Must be an immutable set of names and values (rational: if it's not immutable, rustc cannot possibly know the what to add; this excludes Cargo feature cfg)

    I propose that we continue to follow this rule.


-Zcheck-cfg Stabilization Report
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Summary

This enables checking conditional compilation at compile time by passing a new command line option --check-cfg to all rustc and rustdoc invocations.

In particular this enables checking Cargo features and custom configs with cargo::rustc-check-cfg (in build scripts).

Since rust#117522 setting custom --cfg on RUSTFLAGS no longer has an impact on any crates, only uses of them in the code triggers the warnings.

Example

cargo check
[package]
name = "y"
version = "0.1.0"
edition = "2021"

[features]
lasers = []
zapping = []
#[cfg(feature = "lasers")]  // This condition is expected, as "lasers" is an expected value of `feature`
fn shoot_lasers() {}

#[cfg(feature = "monkeys")] // This condition is UNEXPECTED, as "monkeys" is NOT an expected value of
                            // `feature`
fn write_shakespeare() {}

#[cfg(windosw)]             // This condition is UNEXPECTED, it's supposed to be `windows`
fn win() {}
warning: unexpected `cfg` condition value: `monkeys`
 --> src/lib.rs:4:7
  |
4 | #[cfg(feature = "monkeys")] // This condition is UNEXPECTED, as "monkeys" is NOT an expected valu...
  |       ^^^^^^^^^^^^^^^^^^^
  |
  = note: expected values for `feature` are: `lasers`, `zapping`
  = help: consider adding `monkeys` as a feature in `Cargo.toml`
  = note: see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration
  = note: `#[warn(unexpected_cfgs)]` on by default

warning: unexpected `cfg` condition name: `windosw`
 --> src/lib.rs:8:7
  |
8 | #[cfg(windosw)]             // This condition is UNEXPECTED, it's supposed to be `windows`
  |       ^^^^^^^ help: there is a config with a similar name: `windows`
  |
  = help: consider using a Cargo feature instead or adding `println!("cargo:rustc-check-cfg=cfg(windosw)");` to the top of a `build.rs`
  = note: see <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#check-cfg> for more information about checking conditional configuration

warning: `foo` (lib) generated 2 warnings

Enable-by-default

Per the tracking issue, there is one last but important question to address:

Should these flags be enabled by default? What's the unintrusive interface to provide for users to opt-out the checking?

I propose that the behavior be enabled by default without a way to opt out, otherwise users will probably not see their mistakes in time or not know about the flag at all.

Impact

A crater run was done in rust#120701, the full summary can be found there. The sort version is:

After manually checking 3 categories (most unexpected features, target_*, and general) I wasn't able to find any false positive[3]; in total 9649 actions would need to be taken across 5959 projects (1.4% of all the projects tested), by either: fixing the typo, removing the staled condition, marking as expected the custom cfg, adding the feature to the features table

A Call for testing was also performed and the responses were generaly positive, there were questions about the default set well know names/values and some anti pattern #[path] file sharing, but those are ortogonal to this stabilization.

Mitigations options

However to mitigate it's impact we probably want to annonce the feature ahead of time and provide ahead of time a "migration guide" for users of custom configs.

None

We can consider that the impact is minimal enough and that positives of the feature are strong enough to not do anything more.

Edition bound

We have a edition (2024) that is approaching, we could bound the feature to being enabled on being enabled only for edition>=2024.

on Cargo side

We would only pass --check-cfg on newer editions.

on rustc side

We (Cargo) would always pass --check-cfg but the lint would be allow by default for edition<2024 and warn-by-default for edition>=2024.

Documentation and Testing

The feature is currently documented in the check-cfg section of the Cargo unstable features chapter. Stabilizing the feature would mainly involve documentating cargo::rustc-check-cfg as stable and providing users with a "migration page".

The feature is being extensively tested under tests/testsuite/check_cfg.rs (~20 tests). To give a some highlights:

Real World Usage

Unresolved questions

  • How to deal with the impact of enabling the feature? See above for run down of the options.
  • Should the build script directive warning be appeased ahead of the stabilization ?

  1. For a big part of the existance of the feature, --cfg CLI args were also checked, but given that it interacted badly Cargo RUSTFLAGS, it was removed and left as an future posibility in the documentation. ↩︎

  2. from the Call for testing ↩︎

  3. by "false positive" I mean: missing well known names/values ↩︎

Select a repo