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:
- When compiling a crate, first, all
#[cfg] items are stripped.
- Second, the compiler works as today. Everything has to typecheck and not conflict as such.
- 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?