Open CAD97 opened 2 years ago
This is an interesting proposal, thanks for writing it up. I'll have to think on this.
A small revision to the suggested expansion:
This has the advantage of providing a targeted error explaining that use of the feature requires a --cfg
opt-in (instead of just saying the function is private). A secondary advantage is being a clearly distinct unit able to be cleanly emitted separately, e.g. potentially on an option flag.
It has the downside of duplicating this message for every API gated on the same feature flag. This could be mitigated by the proc macro maintaining a global memo of what feature flags have already had a guard emitted... though I worry both that this mitigation breaks build determinism (e.g. if the macros are handled in a different unspecified order) and might sometimes maintain state between crates (e.g. making whether a feature is gated depend on whether state from a previous compilation is still in cache).
I believe that by requiring a
--cfg
opt in (i.e. inRUSTFLAGS
) it is possible to support "layered unstablility" while preventing "unstability hiding".Specifically: if semver-exempt features are available under (just) a normal cargo feature flag, it is possible that you use some crate
awsm-sauce
under its default features and assume everything is stable, but in actualityawsm-sauce
enables someunstable-risky-feature
in the crateupstream
, exposing you to semver breakage ifupstream
takes advantage of the fact the functionality is marked unstable to change it in an API breaking way. Even though your application did not acknowledge the use of unstable features at all.This is the reason that proc-macro2 requires setting
--cfg procmacro2_semver_exempt
to get access to unstable features.However, extending this pattern to more than one crate is problematic, because while the lack of being able to encapsulate this is desired, it makes writing libraries utilizing upstream unstable features impractical. Say I'm the author
awsm-sauce
: nowupstream
is requiring--cfg upstream_semver_exempt
, and I've realized the error of my ways, so gate the use of unstable parts ofupstream
behind anunstable-*
flag. But now, even though my consumers are reasonably hidden from my use ofupstream
, and they're explicitly opting into my unstable API surface, they have to also opt intoupstream
's unstable surface.My proposed solution: form a consensus that every crate should use the same
--cfg
flag, e.g.allow_crate_unstable
(that's a literalcrate
, not a placeholder for the crate name) orallow_3rd_party_unstable
orcrates_are_semver_exempt
; something illustrative. The point is to have a singular opt-in for builds which unlocks the ability to enable any crate's unstable APIs, just like using the nightly Rust toolchain gives all crates being compiled the ability to enable#![feature]
s.Suggested implementation
Normally annotated functions:
However, for the usage of upstream unstable, we need additional annotation, because the function cannot even be compiled if the upstream does not expose the used unstable item(s).
#[stability::upstream]
is a pseudo-attribute recognized, parsed, and stripped by#[stability::unstable]
.If always requiring
RUSTFLAGS=--cfg allow_crate_unstable
is deemed excessive, we could allow the use of some e.g.this-software-is-provided-as-is-with-no-warranty-of-any-kind
feature alternative to--cfg allow_crate_unstable
, with an attached disclaimer that this disables the protection against hiding instability.