Pre RFC: Crate defined CFG attributes

Currently, cfg attributes are passed in via flags to rustc; however, I believe that there is also a case for crates exposing CFG attributes as well.

For example, eventfd is a function that is available on Linux as of 2.6.27. Rust supports Linux as of 2.6.18, so in order to write crates that are compatible with all platforms that Rust supports, it is not possible to assume the presence of eventfd.

The nix crate provides the bindings to more advanced *nix platform APIs. The nix crate would have a build.rs script that would feature detect whether eventfd is present on the system (though, in practice, it would also allow the user to opt out even if it is present), and conditionally expose the eventfd binding if it is available.

The problem is that, mio would like to use eventfd if it exists, but has no way of knowing whether or not eventfd has been defined.

Cargo features are not a solution to this problem since the availability of eventfd is not opt-in by the user, but detected based on the system. Also, cargo features must be “re-exported” by each dependency where as, this case is really an environmental concern: does eventfd exist on the target platform.

Proposed Solution

What I propose is for crates to be able to expose attributes that are usable by dependencies via the CFG system.

For example, in the nix crate, I could do the following:

#[cfg(has_event_fd)] // detected by builders
attribute event_fd = "yes";

Then, in the mio crate, I could do the following:

extern crate nix;

#[cfg(nix::event_fd)]
// use eventfd

#[cfg(not(nix::event_fd)]
// fallback on using a self pipe strategy

Another option that wouldn’t require a keyword could be to annotate a constant. In the nix crate again:

#[attribute = "event_fd"]
const HAS_EVENTFD: &'static str = "yes";

I’m not sure what the best way to “export” attributes from a crate would be and am hoping to get community feedback!

This feels like it would make more sense to expose as part of Cargo, rather than Rust itself. nix could have a build script that emits something like:

cargo:feature:has_event_fd

All packages that depend on nix would then have a cfg flag named something like nix::has_event_fd enabled for them. The only problem I can think of is that cfg flags might need to be valid identifiers… so maybe nix_has_event_fd?

I agree with @DanielKeep that it may be best to stick to the existing system for ferrying things along, and this may best be done in Cargo instead of the compiler. It’s true that cfg(foo::bar) will not parse today, so that would need an RFC of its own.

Cargo already has support for build scripts to output cargo:rustc-cfg=foo, so Cargo could just do something like:

  • rustc-cfg=meta values are only passed to the current crate
  • rustc-transitive-cfg=meta values are passed to the current crate as is, and are passed to all immediate dependencies as crate_name(meta).

That way nix would print out a number of rustc-transitive-cfg directives and downstream crates could then have something like:

#[cfg(nix(eventfd))]
fn foo() { ... }

(or something like that)

I actually like the idea of having the attributes be exported by the crates, themselves. This would enable detecting which features are available when building against a pre-built rlib, which I don’t think would be possible with a Cargo-only solution.

So @carllerche discussed this with me earlier; my feeling is that the problem is real and we should solve it, but I was also wondering if rustc or cargo was the best place for this. I’ve not made up my mind, but right now I lean towards cargo.

For one thing, #[cfg] currently feels very external to the language right now. If we were going to move it into a rustc, I think I would expect to tie it to constant expressions in general, so that one could just include a reference to an arbitrary constant (though given the interactions with the type checker and so forth this would be tricky, but perhaps not trickier than anything else).

Secondly, if the answer is rustc, this seems to be strongly related to #[macro_use] and friends, in that the “configuration namespace” would presumably be loaded in a similar, global way. Rather than having nix::foo annotations, one might use a similar #[cfg_use] attribute on the extern crate declaration to pull the configuration constants into the namespace (perhaps prefixed). It feels (to me) like these two systems should work analogously, in any case (bad enough we have two namespacing/path schemes, I’d not want three).

However, using the macro system raises some of the same issues. The notation crate::cfg looks like a traditional path but is not, because our paths normally begin at the root, and the crate may have been loaded from somewhere other than the root. Similarly, if the user does extern crate foo as bar, can they do bar::cfg? What if there are multiple aliases to bar, in distinct modules? We can adopt the same sol’n as what macros did for $crate, namely that #[cfg_use] is only available on crates declared at the root level (or at least doesn’t work well), but it’s kind of unfortunate. Doing this sort of propagation at the cargo level kind of sidesteps the issue somewhat.

Finally, it seems like it would be useful to have something like #[cfg] (and possibly #[cfg] itself) that works at parse time. That clearly interacts with this proposal because the new syntax may appear before the extern crate declaration, and the scoping would be kind of odd.

I want to emphasize what @rkjnsn said. A Cargo only based solution will not work with pre-built crates. I’m not exactly sure what the long term plan for supporting pre-built crates is, but it seems worth highlighting.

It seems like a cargo-based solution could work just fine with pre-build crates, but you would have to either

  1. distribute another file or
  2. have rustc embed the cfg into the rlib metadata (which cargo could extract)

How would rustc support this? Would it just allow embedding arbitrary data in rlibs? Is that something we want? Or would there be special support for Cargo configuration data? If the latter, I’d definitely prefer the functionality to be entirely supported by rustc.

There will always be Rust code that’s not compiled with Cargo for whatever reason, and I think such code should still be able to support different configurations in rlib dependencies.

I’m not sure, however, if using the existing #[cfg] is the best approach, since, as you note, it seems somewhat external at the moment. Perhaps it would make sense to solve this problem as part of a general approach to compile-time introspection.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.