`cfg(extern = "rand")`

I don't have the time to drive this atm; I'm mostly posting as a place to reference the idea and in case anyone else wants to pick it up.

Some cfg expressions can get complex or repetitive of specific data. cfg_select! and eventually cfg aliases can help with this but sometimes your cfg expressions in Rust code also need to mirror cfg expression and feature activations from your Cargo.toml. What if rustc made available the accessible extern names for the crate being compiled as an extern cfg set of values? Your Rust code can now reference the end result of cfg expressions and feature activations from your Cargo.toml.

Say we had cfg(rust_version), this means you could do:

[target.'cfg(rust_version("1.20"))'.dependencies]
foo = "1"
fn foo_wrapper() {
    #[cfg(extern = "foo")]  // in place of `#[cfg(rust_version("1.20"))]`
    {
        foo::foo()
    }
    #[cfg(not(extern = "foo"))]  // in place of `#[cfg(not(rust_version("1.20")))]`
    {
        // ... some kind of polyfill logic
    }
}

Of course, some potential alternatives would be

  • A cfg alias system in Cargo.toml that gets mirrored over to Rust code
  • A cfg_select! system in Cargo.toml to make cfg aliases easier to express

These alternatives are likely more complex to design and would be further out for us to take advantage of. They are also extending some low level features further into Cargo and there would need to be some deliberation of what kind of line should be drawn for that.

2 Likes

This is functionally identical to cfg aliases, right? How is it any different?

There are any number of things you might use to control the presence of an external dependency. The logic for that can be controlled in arbitrary ways by the build system. This sidesteps duplicating that logic into your code even once (which cfg aliases would still require), and instead allows you to check whether rustc got supplied the crate as an extern.

The main disadvantage compared to cfg aliases is that you can only use this approach if you actually have a dependency that depends on this cfg combination. It doesn't seem that different from just having a way to define --cfg flags from the toml directly:

[target.'cfg(rust_version("1.20"))'.cfgs]
foo = "true"
1 Like

Another thought: In Tokio we often use cfgs that honestly could just be cfg(feature = "foo), but they end up more complicated because the feature has a dependency on something else:

  • Only legal if you pass --cfg tokio_unstable
  • Only legal on some targets (e.g. wasm only)
  • We don't have cfg(rust_version), not but if we did I could imagine wanting a higher MSRV for specific feature flags as well.

We can implement this by having a

#[cfg(all(feature = "foo", not(bar)))]
compile_error!("foo requires bar");

And then I'd like to just use feature = "foo" in the rest of the crate. But I can't actually do that because when the dependencies are not satisfied, I just want to output one compiler error, so that the user clearly sees the foo requires bar message. Usually I have to do cfg(all(feature = "foo", bar)) everywhere that needs the feature because otherwise it prints a bajillion errors from random other irrelevant issues.

This is getting into

They are also extending some low level features further into Cargo and there would need to be some deliberation of what kind of line should be drawn for that.

We've previously rejected a [cfg] table. Cargo focuses on abstractions of rustc features, for example we have features for defining one kind of cfg and we want globals / package parameters as another kind of cfg (Pre-RFC: Mutually-excusive, global features).