Fleshing out libstd "scenarios"


For the record, the initial versions of glium were using a similar system but with Cargo features. For example if you wanted to use geometry shaders, you’d enable the geometry_shaders feature. There were around ten to fifteen different features if I remember correctly.

I ended up dropping the concept pretty quickly because of the negative feedback, as the documentation became messy and people were getting confused by the system.


Forgive me for being cranky (perhaps its too early in the morning), but I think we need less strawman syntax and more mathematics. I see some syntax, but little clarified semantics since the last post.

Additionally, I’m thinking that #[cfg] is entirely orthogonal to #[scenario]. We can see here that the methods are tagged with both, but you could imagine #[scenario] implying #[cfg] perhaps eventually.

Uh, sounds like they are not orthogonal? What is the meaning of cfg without scenario, or scenario without cfg?

Here we see a crate that activates the unix and windows scenarios`

This gets confusing when we normally think of #![foo] as applying #[foo] to all items. I believe this currently holds for all items. I think we can restore this convention, but more on that in a bit.

I would also imagine that there’s sort of a global namespace for scenarios.

Woah, this is very dangerous! To preserve modularity if any crate can define features I think we need to namespace by crate. Otherwise we end up with a situation where nobody but the standard library can confidently take a name from the global namespace.

Ok, here are my design sketch. First, an aside: I don’t see how there won’t be really awkward overlap with scenarios and features and + built-in CFG tokens. The former is are nice per-crate black box variables, and the latter is nicely connected to target definitions. Scenarios clearly will need some elements of both, so hopefully we can flesh out the division of labor once we figure out what scenarios mean.

Second, the problem. When we say code is portable, we mean one of two things. The first, and arguably original, type of portability might be deemed parametric. This means the code “simply doesn’t do anything non-portable”, or more formally, it doesn’t use any cfg'd/scenario'd item or language construct, and is thus trivially guaranteed to work on all systems. The second would be non-parametric, where the code attempts to maintain compatibility by defining piecewise its implementation–i.e. a bunch of cfg’d versions of internal methods.

The second case usually isn’t as good, because the cfg assumptions are rarely pred and not(pred), but rather a bunch of different predicates with the hope that they are disjoint and at least one is true. This hope invariably is not true forever as new systems are introduced which play with basic assumptions in various ways. At the same time, the second case is inevitable when writing software. The problem is we want to verify the portability of code without brute force compiling on different platforms, because that is intractable.

Without trying to sound academic, I firmly believe the essence of the scenarios problem is satisfiability modulo theories. I mean less the associations of off-the-shelf solvers, than the core mathematical concept. Plain boolean satisfiability is “does this formula hold true under any interpretation”, SMT is “does this formula hold true under any interpretation consistent with the theory”–i.e. “does this formula hold true under any of these interpretations I actually care about”. What is a formula? For our purposes it is an boolean expression (no quantifiers)…like a cfg formula. What are the variables? Primitive CFG tokens, attached by the compiler to targets, library-defined features, and perhaps something else if we need it (I think we won’t). What are the theories/sets-of-interpretations? Basic axioms like and(windows, unix) = false [though @eternaleye will shut down that example beautifully]. What does SMT become when you negate your formula? “does this formula hold true under all of these interpretations I actually care about”. What do you get putting it all together? “does this cfg hold true under all of the possible truthinesses of cfg-primitives/features I care about”. Boom! That’s portability.

Wait!, you ask. My code has many cfgs, not just one! How can that be portability?! Here’s how. Recall how name resolution works: ignoring things like globs and shadowing (cause what twisted soul would use those in conjunction with #[cfg(..)]), we need all identifiers in an item to resolve to exactly one item. This means we need cfg of each item to imply that exactly one of the cfgs of everything it could possbility resolve to is true:

cfg-of-item => one-of(cfg-of-helper-v1, cfg-of-helper-v2, ...) 

Now => is straight up propositional logic, and one-of can be elaborated as propositional logic, so via this means we can derive a portability formula for each item in a crate. The standard interpretation of #![foo] actually works in that we are simply asking all items in the module to be portability up to this cfg-theory (but yes, we may want to remove the module instead of making it empty, I don’t know).

Ok, I got to go to work so no time to proof-read or talk about anything else. Later I will dive into what theories Cargo’s current resolution algorithm implies, and what to think about target specs / builtin configs (@nagisa’s RFC!).


If #[scenarios] can somehow support my requirements detailed below then I am all for it.


I disagree, here. IMO, there’s a clear third pole here, and one that’s underserved. To wit:

  • Built-in cfg tokens are connected to target definitions, and both their existence and value is determined by the target
  • Feature flags are connected to individual crates, but while their existence is determined by the crate, their value (whether they are enabled) is controlled by Cargo. This is important to note, as due to the additive nature the user has no way to mandate a feature be disabled.
  • Scenarios, as I envision them, are connected to root crates. While they are defined… elsewhere (and that’s a can of worms), their value would be specified by the root crate, and they would place an upper bound on features. Features like asm or std should depend on the appropriate scenario, and when a scenario is not in the bounding set defined by the root crate, Cargo would be forbidden from enabling features that depend on it.

In that model, Rust code would never see scenarios directly. They are an additional input to Cargo’s feature-enabling algorithm… and that’s entirely sufficient for every use case I’ve seen.

Second, the problem. When we say code is portable, we mean one of two things. […] The second would be non-parametric, where the code attempts to maintain compatibility by defining piecewise its implementation–i.e. a bunch of cfg’d versions of internal methods.

I’ll note that seeing scenarios as being about portability is, IMO, a mistake, largely because it leads people towards using them for the second approach. If they’re going to go down that (painful) road, then the hard-wired cfg is better at it.

My personal view is that scenarios serve best when framed as being about operating under constraints. I want to operate in some domain that requires Rust code not do a thing. Whether that’s “attempt to use assembly”, “call a memory allocator”, “use WinAPI calls”, or anything else. There is currently no mechanism for this at all.

Basic axioms like and(windows, unix) = false [though @eternaleye will shut down that example beautifully].

Indeed I shall! Making this not an axiom is rather the whole point of the midipix project, which not only provides a POSIX-satisfying C library on Windows (by reimplementing the Linux syscall ABI in terms of Windows calls, and then putting musl on top), but also provides support for cleanly calling the Windows APIs in a seamless manner, even up to using UTF-8 for both sets of APIs.


Very good points! I view the two systems as orthogonal in the sense that #[cfg] runs first, cleaning out good chunks of the AST. Afterwards we’ve got items tagged with #[scenario] that then get propagated through.

In that sense I think rustdoc would definitely want to render scenarios. The documentation for the standard library would then, for example, just simply show all Unix-specific functions (like as_raw_fd) as "this requires the unix scenario).

Note that this is not unlike today’s rendering of documentation of the standard library where AsRawFd is an implemented trait for types like File. Also note that we would definitely still need to publish Windows documentation!

Ah this may actually be explained by my above comment as well. I’m at least envisioning that #[cfg] still runs to prune out and strip the AST of unconfigured items. What remains is #[scenario]-tagged items. I think of it as #[cfg] will omit items from compilation, and #[scenario] will omit items from downstream consumers (but still compile them)

Another very good question! I think we can boil down the backcompat story into two categories. Those APIs which today are platform-specific (e.g. std::os) and those that aren’t (e.g. std::thread). For the first category the entire std::os module will not require scenario to use, but scenario-requiring methods and traits in their normal location (e.g. AtomicU8) will be added.

For the second category I think this is what falls under the “default scenario” category. By default the standard library will give you floats, threads, etc. Some platforms, however, just fundamentally don’t have these primitives (like threads on emscripten, for now). These platforms would simply have modules disappear under this scheme. I’m currently under the impression that all these targets are nightly anyway so breakage is either expected and/or ok.

Good points! I wonder if we could perhaps enhance custom target specifications in this regard? E.g. if we have the standard library sliced up into a number of scenario categories, you could imagine that any platform could pick and choose what slices of the standard library are compiled in at will? I’m not sure if this covers all the use cases you’re thinking about, however.

This is an interesting idea! I feel though that this is actually a separate feature we might also want to have. This helps I think when you want to opportunistically work on a platform that you don’t even know about (e.g. automatically work on platforms without threads, but not specifically work around emscripten itself).

Independently though I think scenarios are still worthwhile as one of the major features of them is gated APIs by default. That is, you can’t accidentally use as_raw_fd even though it’s available. Put another way, you can’t accidentally make your code less portable by default at least.

Now I think we’d definitely want a feature like this for various scenarios:

  • Working across multiple versions of the standard library
  • Working generically across platforms that don’t have a particular feature instead of listing them out exhaustively

This can be emulated today with a build script that does this sort of detection, but it’d be great to have a standard feature for it yeah.

Initially I think this sounds like a great idea, but I think it ends up being too conservative to be practical. In some sense the standard library is going to provide something by default, so there’s a “default scenario” no matter what. In that case, what’s that default scenario? For example you could imagine platforms that don’t have threads, floats, 64 bit integers, etc. In other words, crucial bits of the standard library to operate.

So to me I see a spectrum here of on one end there’s 8 bit processors from the 80s, and on the other hand there’s x86_64 Linux on Intel’s newest chip today. The line of what the standard library provides by default is somewhere on this spectrum, and either extreme is too unergonomic to work with.

That all leads me to the conclusion that today’s choice of APIs provided by the standard library strike a reasonable balance between ergonomic to use and reasonably portable. It’s of course the wrong decision for some maintainers who want libraries to work in as many areas as possible, but it’s very much the “right” decision for others writing, e.g. CLI apps (say, Cargo and/or rustc).

Note though that the intention is to definitely accommodate developers who work across tons of platforms though. The “opt in” would in theory just be a line or two at the top of the crate indicating that the default scenario should be turned off.

I envision the compiler working in a few ways:

  1. When compiling a crate, first, all #[cfg] items are stripped.
  2. Second, the compiler works as today. Everything has to typecheck and not conflict as such.
  3. When loading a crate as a dependency, you’ll always load a crate with a set of scenarios active for that crate. Only configured APIs (e.g. activated scenarios) are loaded from the crate.

In that sense your code example would not compile because cross_platform_fn is defined twice. You’d have to use #[cfg] to select between the two there. I see #[scenario] as unlocking functionality, not changing implementations (like #[cfg] does).

True yeah, although I’m realizing now that I’ve not been thinking about this in the sense of changing functionality. I’m envisioning scenarios as we’re shipping to you an API which is as full-featured for the platform as it can possibly be, it’s just that some of it’s turned off because we don’t want you to be able to use it by default.

Interesting! I’d be curious to dig more into what’s going on here. The documentation I see as a critical point which we need to solve, but I think it will also be a very simple one to solve (e.g. rustdoc just renders all #[scenario]-tagged items specially like it does with #[stable] today).

Could you elaborate more on the confusion you were seeing though? Perhaps there’s kernels of confusion we could head off?


I would not generally think of it like this - in terms of platforms, and purely additive - but in terms of features. Rather I would think of it as “this is a crate that works without the windows/unix/threads/networking” scenarios. The “default” scenario, that all present (non-no-std?) crates are assumed to operate under is the “windows/unix/threads/networking” scenario. In the future there will be scenarios that subtract parts of std, and thus subtract parts of the std scenario. So the emscripten std would not declare support for any of the aforementioned scenarios. Any emscripten-supporting crates would need to declare they work without chunks of std, vaguelly like

#![scenario(core, collections, alloc, rand)] // For an additive declaration started  from a position of "no-scenarios"
#![scenario(without(threads, networking))] // Declaring which features you can live without

In the subtractive case, the compiler won’t let you build until you’ve subtracted all the parts that the emscripten port doesn’t support. And you can imagine further declaring (somewhere) “I want to support this set of platforms”, and have cargo/rustc tell you (without having actual cross-compile capability to that platform) “no, you are not in the right scenario, do these things to fix”.

This ability to have the toolchain statically tell you which platforms you can support without actually attempting to compile I think is the thing we need out of scenarios. Otherwise it’s just a reformulation of cfg.


In the subtractive case, the compiler won’t let you build until you’ve subtracted all the parts that the emscripten port doesn’t support.

The problem is that this doesn’t axiomatize: What if I’m using a crate that provides RDMA, and I want to build “without infiniband” (for any number of reasons - licensing, not having the hardware, etc)?

The only way what you propose could work is if the set of scenarios is closed, and only extensible by rustc, in the definition of target specs. I feel that would cripple scenarios, which could (IMO) fill a desperately needed role of allowing root crates to limit Cargo’s additive feature behavior. See also, a couple of issues recently where the lack of such an ability has made Cargo considerably less ergonomic for crate authors.


The ability for the root crate to enable and disable scenarios/features and have that control how upstream crates are compiled would be amazing. Imagine being able to disable the openssl scenario without having to manually go through every single dependency to ensure it doesn’t forget to disable the default features of some other dependency so it can disable the default features of yet another dependency so that it doesn’t depend on the openssl crate. Features are great for enabling required functionality, but are terrible for optional functionality.


I’m afraid I don’t know what the relationship between RDMA and infiniband is so I have a hard time visualizing what you are describing. I might imagine the RDMA crate either does or does not declare the infiniband scenario, and the downstream crate does or does not consume it.

The way I’ve imagined scenarios is as a relatively simple effect system propagated through the dag. For any compilation configuration, a crate may or may not introduce a scenario, which is propagated downstream. Downstream crates must then match, for the compilation configuration, the set of scenarios they require with the scenarios present in the world. (Hm, to that end I might expect scenarios to be declared in Cargo.toml, not in source, and enforced entirely within cargo).


To be honest I didn’t elaborate because I don’t really remember exactly what was happening (it’s been a long time, and since the library was in heavy development I didn’t document all the changes correctly).

I think the biggest problem is that users were trying to use functions that are present in the documentation without realizing that they needed to tweak their Cargo.toml to enable the corresponding feature.

In the case of glium, the problem is that the error that the user would get is that the function simply doesn’t exist. If you integrate “scenarios” in the core of Rust, that would be solved by instead returning an error saying that they need to enable the corresponding scenario.


(Disclaimer: I don’t write libraries and don’t use cargo in non-trivial ways.) Cargo features don’t have values? Something like “USE_OPENSSL=0”? Because what you are describing sounds very similar to this thing from cmake

target_compile_definitions(my_root_crate PUBLIC USE_OPENSSL=0)


RDMA is a general term for “remote direct memory access”. There are a number of technologies this can be done over, including Fibre Channel, Infiniband, and Ethernet.

I’m not clear on what you mean by “consume” here - it sounds like you intend these to propagate in the same direction and manner as features (dependencies declare them, direct dependents enable/disable them), and are arguing in favor of a “default on, opt out” semantics.

In my opinion, this would not work well: That propagation manner is insufficient (dependencies declaring them mandates direct dependents enable/disable them due to namespacing issues; if direct dependents control them, they’re isomorphic to features; if they’re isomorphic to features I see minimal benefit), and opt out is dangerous with any other propagation manner (because if I’m building a kernel, I know what is available, but I don’t know what crazy things my Nth-level dependencies might want to be available).

They do not - they are all boolean, and are enabled by cargo if any dependent requires them. This is why negative-polarity options (no_foo) are dangerous/invalid.


No, cargo features don’t have values. They are either enabled or disabled.

The problem is that some dependency might depend on hyper without disabling the default features, effectively forcing hyper to always enable the openssl feature, despite hyper and that dependency both being able to work without that feature. Once enabled by a downstream dependency, that feature is stuck enabled and the user has no control over it unless that dependency provides its own features to enable/disable the features of its dependencies (and all the way transitively until you reach the root crate) which is hell. If even one dependency fails to provide this, everything is ruined unless he user or root crate vendors their own version of that dependency to disable the default features of hyper and have its own feature to enable the openssl feature of hyper. For complex iron projects this process might have to be repeated for multiple dependencies, and can get quite hairy when the problematic dependency is several layers deep.

What Cargo needs is several things:

  1. A way to distinguish between features that are required and optional features that are nice to have.
  2. A way for upstream crates to tell downstream crates which features are actually enabled, so they can take advantage of optional features when they are enabled.
  3. A way for the user or root crate to enable and disable those optional features.

As a bonus, it would be really cool if the build script for a crate could choose which optional features to enable.


What you want is a “program-composable” way of showing that code does not do a thing - if every individual part of your code is malloc-free, then your entire program is malloc-free (as opposed to ways that show an individual function of a malloc-containing program is malloc-free, aka “purity”).


Have we just been using the different terminology for the same thing the whole time :)? To me this is a) better namespacing features with the crate they are defined in, and b) allowing downstream crates (especially root, but perhaps not limited) to “veto” features.


I think this may actually be an important point to work out. My interpretation of this is that you’d like crates to declare which platforms they support, right? If not, then you can ignore the rest of this, but if so, I’m curious! So on one hand this is nice where if I explicitly support a platform I can’t accidentally regress support. On the other hand if I have to explicitly support a platform then adding a new platform means the whole world has to be updated to work with it. (just an interesting tradeoff I think should be considered)

I’m also curious about the implications for subtractive scenarios. Let’s say that your crate works on both emscripten and on Windows. When you’re on emscripten you avoid threads but on Windows you use them as a performance optimization. What would the scenario opt-in look like here? Are you thinking you’d have something like:

#![cfg_attr(target_os = "emscripten", scenario(without(threads)))]

I’d personally prefer to avoid mixing #[cfg] and #[scenario] like this but I’m curious what you think!

Ok, thanks for the info! Sounds like high quality documentation and error messages are critical to this features success, in any rate.


To be clear, I don’t think scenarios is suitable for the winapi use case you’ve outlined. The intention here (at least for the standard library) is to allow access to APIs that are otherwise available by default. Now this might mean that the scenario feature above isn’t as ambitious as it could be, but as I’m thinking of it so far at least it does not cover the use case of selecting what the API of a crate should be (e.g. disjoint sets).


Just chiming in with some stream of consciousness:

  1. I wish I could tag feature-flagged modules/items/whatever with a message, or even the compiler has a default message like “did you enable ?” e.g. having a hint here would be nice, but maybe it’s impractical:
error[E0432]: unresolved import `elf::strtab::Strtab`
--> src/image.rs:19:5
19 | use elf::strtab::Strtab;
 |     ^^^^^^^^^^^^^^^^^^^ Could not find `strtab` in `goblin::elf64`

  1. I like the idea of a global namespace for certain feature flags, like “std” I’m noticing some people are spontaneously adopting for allowing no_std situations, but I don’t like the idea of global namespaces in general for many reasons listed above, but also the whole resource squatting thing if that hasn’t been mentioned. Must be very careful with this.
  2. I love/love/love the idea of documentation illustrating what/when/why/how it is disabled. This seems like an oversight that it isn’t already incorporated
  3. The more I think about negative polarities in feature flags with current behavior of cargo, the more it scares me. I’m quite grateful @eternaleye pointed out the error of my ways, but I’m not sure how to prevent authors from doing it…
  4. So I’m working on a new archive api, and it uses the std and endian_fd feature flags. I’d like users to know this automatically (i.e., in documentation) without my having to type it. I almost wish feature-flags themselves were simply documented/documentable in the top crates documentation…

Maybe some or all of these issues will be resolved with scenarios, not sure. If it improves the documentation landscape on feature flags at all then I’ll be happy :slight_smile:


I’m not sure if I completely understand what #[scenario] does, so I’ll just describe two cases I would like it to solve :slight_smile:

  1. I develop on Mac and test on Linux, but I don’t have Windows machine to test on. I would like Rust to warn me when I accidentally write code that does not compile on Windows.

     #![scenario(unix, windows)]  
      // #[cfg(unix)] - oops forgot the attribute
     fn cross_platform_method(f: &File) {
         let fd = f.as_raw_fd(); // I expect: Error: would not compile on `windows`
  2. I develop and test on a 64-bit machine, but I want a strong guarantee that my code will never overflow on a 32-bit machine. Currently this very hard to do (mainly because use of usize is unavoidable, the as operator is an unchecked cast, and Into doesn’t work in this context).

    I imagine the scenarios mechanism could be used to state “This code must compile on both 32-bit and 64-bit” and “Only 32-bit and 64-bit architectures are supported” (i.e. 16-bt/128-bit may fail to compile and that’s OK). With this the stdlib could add/remove Into implementations as appropriate, so that as-free code is forced to use TryInto where it could lose precision on 32-bit, and also would opt-in to Into implementations that are currently missing due to hypothetical 16-bit and 128-bit support.


So, I’ve been thinking a bit more about this proposal, and I think there is a way to extend it to give stronger, statically checkable, inter-crate guarantees that address some of the concerns I’ve seen about the proposal as-is.

I’m not sure how feasible, or even internally consistent these idea are, but here is what I got:

  • A crate can define any number of scenarios by name.
  • Scenarios can be transparent aliases for combinations of other scenarios.
  • Said combinations would be logical formulas with the operators any() and all() from #[cfg], but additionally also support either() to express exclusivity.
  • Scenarios defined in different crates that share the same name are distinct from each other.
  • Scenarios can be imported from an extern crate to use them in more than one crate.
  • Every single item in a crate gets “tagged” as belonging to one or more scenarios.
  • To make this more manageable, every crate can declare an “implicit scenario” that gets applied to all items automatically.
  • To make this backwards compatible, the implicit scenario defaults to one defined in std, which represents the currently in the community assumed “desktop” scenario of being cross platform compatible for the major platforms linux, windows and MacOS.
  • Scenario-tagged items transitively check that their definition does not access items for scenarios more constrained than them self.
  • A crate has to tag itself with all scenario combinations it supports, with a warning lint that enforces it in a backwards compatible way by checking all items in the crate. This also the serves as a mechanism for enabling scenarios, by supporting more than the default one.
  • The cargo repo would start rejecting new versions of crates that emit that warning, similar to the “*” version dependency check. This ensures gradual transition to the new system.
  • #[cfg] also gains the properties of a scenario tag, and would be checked in the same way. Enabling them per compiler flag would probably have to implicitly define their scenario, with a warning lint demanding explicitness.
  • The compiler would start remembering #[cfg]d items in some fashion, so that the scenario checker can reason about their existence for the crate-wide check.
  • Different #[cfg] flags on items with the same name would cause a “virtual” scenario tag expressing the constraint “either of these needs to be defined”.

This seems like a somewhat complex system, but by picking the right defaults it should be manageable. (And has to be ignore-able anyway, for backwards compatibility)



// [crate std]

// Import scenarios.
// Attribute behaves like macro_import for selecting either all or a subset.
extern crate core;

// Declare scenarios "atoms", that might correspond to cfg flags
// ...

// Declare scenario aliases
#![declare_scenario(desktop, all(either(unix, windows), thread, /* ... */)))]
#![declare_scenario(emscripten, all(floats, core, /* ... */))))]

// Require explicit annotations for all items in std

// Restrict the std library to a number of scenarios.
// These check the crate assuming only one of them is active at any given time.
#![crate_scenario(all(desktop, unix))]
#![crate_scenario(all(desktop, windows))]

// ...

impl File {
    pub fn new() -> Self;

    #[scenario(all(desktop, unix))]
    pub fn as_raw_fd(&self) -> c_int {

    #[scenario(all(desktop, windows))]
    pub fn as_raw_handle(&self) -> *mut HANDLE {

Crate with additional features (optional functionality)

// [crate foo]

// Implicit:
// #![scenario_import] extern crate std;
// #![implicit_scenario(desktop)]
// #![crate_scenario(desktop)]
// #[scenario(desktop)] on each item


// Implicitly translated as
// #![crate_scenario(and(desktop, extra))]
// because of the implicit desktop scenario

// Implicit translated as #[scenario(all(desktop, extra))]
fn feature() {
    // ...

Crate with reduced features (emscripten compatibility)

// [crate bar]

// Replace the default "desktop" scenario for this crate
// Implies #![crate_scenario(emscripten)] and
// #[scneario(emscripten)] on all items

// Dependency is checked to support the emscripten scenario
extern crate dependency;

// ...

Crate that abstracts over different platforms

// [crate cross_plattform]

// Implicit:
// #![scenario_import] extern crate std;
// #![implicit_scenario(desktop)]
// #![crate_scenario(desktop)]
// #[scenario(desktop)] on each item

// Implicitly:
//    #[scenario(all(desktop, windows))]
// == #[scenario(all(all(either(unix, windows), thread, ...), windows))]
// == #[scenario(all(windows, thread, ...))]
fn cross_platform_fn() {
    // ...

// Implicitly:
//    #[scenario(all(desktop, unix))]
// == #[scenario(all(all(either(unix, windows), thread, ...), windows))]
// == #[scenario(all(unix, thread, ...))]
fn cross_platform_fn() {
    // ...

// Both above implicitly define
//    #[scenario(either(all(unix, thread, ...), all(windows, thread, ...)))]
// == #[scenario(all(either(windows, unix), thread, ...)]
// == #[scenario(desktop]
// for the item `cross_platform_fn`

// Implicit #[scenario(desktop)], so check ok.
fn cross_platform_fn_2() {

Crate that causes various errors

// ERROR: Crate implicitly declares itself as belonging to the desktop scenario,
//        but some of its items are only available for the all(desktop, unix) scenario.
// NOTE:  Consider adding #![crate_scenario(unix)] to optionally support the unix scenario.

// ERROR: Access to item b is only enabled for scenario unix,
//        but a implicitly declare itself for the "desktop" scenario
//        which does not imply unix
// NOTE:  Consider adding #[scenario(unix)] to constrain a() to the unix scenario
// NOTE:  Consider adding a implementation of b for the windows scenario
//        to fulfill the desktop scenario
fn a() {

fn b() {

Winapi families

This is based on the constraints given here.

// [crate winapi]


// [crate winapi_consuming_lib]

extern crate winapi;


fn cross_platform_fn() {}

fn cross_platform_fn() {}

// [crate binary]

extern crate winapi;
extern crate winapi_consuming_lib;


What makes this example a bit tricky is that this scenario system only checks for validity, but can not trigger configured builds. That is cargos domain, so the top-level selection of the family would probably have to happen with a cargo feature that gets passed through to all upstream crates up to winapi itself. (Not sure if the current feature system makes this feasible though).


This would be terribly impractical and unfeasible. Expecting every single crate in all the chains from the root crate to winapi to pass along a feature for which family to select is a nightmare. There needs to be a way for the user or root crate to select a family without any dependencies along that chain having to care. At most, crates which depend upon winapi directly should be informed of the choice of family.