Reflecting on reactions to build probes & cfg-gating features

I recently started a thread which branched over to a discussion on build scripts, and particularly feature probes. I was quite taken aback by the visceral reaction this topic caused and, given that I keep thinking about it a week later, I wanted to put a few controversial thoughts out there and raise a bit of a mirror to this community.

I get that there is a serious problem with broken feature probes in build scripts. If the compiler needs to implement a workaround/hack to help people cope with an easily-misused lib[1] / badly thought out build scripts then something is badly wrong.

The technical situation as I understand it

  • Rust has a clear stability-first, opt-in approach to experimental features consisting of the following:
    • by default stable, edition 2015
    • optionally, override toolchain and/or edition to obtain blanket access to additional features
    • optionally, lock-down access to experimental features on nightly by specifying an empty whitelist or allow access to specific experimental features on nightly by adding them to the optional whitelist
  • Many[2] libs include build scripts which try to identify whether a feature is available, make use of it if it is or fall back to reduced functionality if it is not
  • Many[2:1] build scripts simply rust some kind of version number check rather than a compilation probe
  • The only safe way to identify whether a feature is available is via a build probe but ...
  • Cargo provides an option to specify an allowed-features whitelist but does not expose this to build scripts nor does it force-inherit them for build scripts via RUSTFLAGS
  • Cargo has decided to actively avoid implementing any changes which are targetted at supporting build probes

Community reaction

  • Any attempt to ask for advice and support creating a build probe is met with vehement disapproval
  • There is next to no documentation on how to create a good build probe
  • Aggressive issues are opened on repos with minimal background, no consideration of the use case which lead to the build script inclusion or support for finding a solution[3]
  • This is vastly different from the positive community I have experienced around everytihng else in rust
  • I feel the need to add a positive shout-out to cargo and epage[3:1] for a positive attitude while clearly walking an impossible tight-rope

The inherent footgun

  • I tried to create a selection of genuinely good probes, in a way which was safe and even respected cargo's hidden allow-features whitelist
  • I went out of my way to work around the missing documentation and obfuscation
  • I published this as a library[3:2] with a deliberately restricted API which forces good practices
  • I still feel vilified, instead of being thanked for trying to make a bad situation a chunk better
  • So why would anyone go to the hassle of trying to work out how to not break things if they can't find docs and can't ask for help? Most won't they will just stick with what is available, popular and obvious. They won't even know about edge-cases and pitfalls as there is no way to find out about them until they get an agressive issue opened on their repo with no support in resolving it or understanding for their use case.

Example use case for build probes

Most situations[2:2] resolve around the wish of a crate author to provide a stable, ergonomic API without breaking things for the wide-range of downstream crates they (hope to) have.

  • Let's say I have a crate with MSRV 1.85.1.
  • I currently make use of assert!(matches!(...)) in places which create visible output (maybe I'm creating a test framework ...)
  • I want to take advantage of the recently stabilised assert_matches!()[4] to get better rendering on failures
  • I prioritise having a stable API, ease-of-use and supporting as low an MSRV as possible
  • I'm willing to put in the extra effort to cfg-gate the new code and leave the existing code in place as a fallback
  • My options are:
    • update my msrv to 1.97.0 and force all my users to upgrade their msrv for any future bugfixes, features etc.
    • update my msrv to 1.96.0 and additionally accept that I could break for someone using a pinned nightly which is 1.96.0 but from before the stabilisation PR
    • add a "viral" --cfg flag which needs to be set manually at build time and cannot be influenced or relied upon by my direct dependents
    • implement a probe in my build.rs to identify the availability & location of assert_matches! and set the cfg accordingly

To my mind the final option is the one which best embodies rust's attitude to stability, repeatability and developer-productivity.

tl;dr

The current community reaction to any discussion of this nature effectively promotes badly implemented cfg gates making attempts to do them well too painful and hiding the information needed.


  1. interestingly this very popular lib is referenced positively in the cargo book ↩︎

  2. based purely on empirical evidence and no statistical analysis ↩︎ ↩︎ ↩︎

  3. I am deliberately not @-tagging anyone or linking any crates or repos on this post ↩︎ ↩︎ ↩︎

  4. Deliberate example as probing safely for assert_matches is particularly complicated ↩︎

I'm sorry if my posts came across as aggressive, I do know I have a particularly hardline stance against unstable-build-probes. Partly I'm just tired after years of having to add workarounds for crate after crate that implement them in different ways. I don't provide many details in the issues I open about them because I consider unstable-build-probes an extremely advanced feature of the toolchain to be using (and often semi-recognize the authors of the crates).

One thing I would like to separate is msrv-build-probes and unstable-build-probes. I understand much more wanting to use an msrv-build-probe to support old compilers and those are generally much more reliable because they're working against a stable known API. I don't believe I have actually run into a case where they have broken my build compared to at least once a year running into issues with unstable-build-probes.

Really if a crate must have build probes the minimum I would love to have is an opt-out (preferably a single env-var shared across all crates implementing build probes) I_PROMISE_I_AM_USING_A_RUST_TOOLCHAIN_THAT_PROVIDES_THE_LATEST_STABLE_APIS_AND_DONT_WANT_UNSTABLE=true that can skip both main reasons for build probes and just compile the latest stable version of the codebase.

1 Like

That would still:

  • only support one specific use case and not help others - e.g. those with "every unstable feature costs me x00'000$ in risk assessment, approval and ongoing management costs; but I need a few"
  • require build scripts to actively buy in to checking this

unless cargo enforced it - at which point it would be affected by the same "helping build scripts probe is not currently considered a good thing" policy which means allow-features, edition, etc... are not available

But where is the line and the difference?

  • If I wait until it's fully stabilised before beginning to implement but still put in a build probe - this needs to work with people on an older pinned nightly
  • If I decide to start setting up my usage of the newly stabilised feature in the 2 weeks while it is in beta - so I get a bit of testing
  • If I decide to implement earlier - so I can experiment properly and be sure that it's nice and stable for when the feature hits mainstream

It's seems to blur together and be rather grey to me.

If you never use #![feature] then it’s an msrv probe, if you do then it’s an unstable probe.

Experimenting with a feature while it’s unstable and then also supporting the stabilized form is fine, but should be treated separately, it’s not uncommon for a feature to change its shape during stabilization so the unstable probe would no longer apply anyway.