[Pre-RFC] Crate/item stability

Looking for feedback on this.

Rendered

1 Like

What does “reserved” mean?

#[reserved] means “reserved for future use”. It’s meant to be used on struct members, bitflags etc.

…actually, it probably only makes sense to use it on such items. Maybe it deserves its own RFC?

EDIT: I also updated the text with a short description of each attribute.

Probably. Why a crate would need to explicitly reserve something isn’t obvious, at least to me.

Okay, it’s gone now.

I’ve also updated the RFC to allow multiple stability attributes on one item, because as it turns out, that’s how the internal stability attributes currently work. This means that since is now required on all stability attributes, which is a breaking change because that applies to #[deprecated] as well (it currently doesn’t require any fields). I think it’ll be okay though because #[deprecated] itself is still fairly new. (As usual this won’t be a hard error at first - that’ll be left to another RFC - but that’s only for items that have exactly one stability attribute, because the item stability rules assume that since is present for all stability attributes).

Why restrict the since field to semver version numbers? More generic version comparison is possible. (Yes, I know crates.io requires semver. I don’t know if that means everyone creating a crate uses it.)

I’m not sure that I like unmarked items being automatically considered stable. Why not have a third stability level for internal use, unspecified? From the point of view of compiling a stable library, Rust should warn about unspecified items in the public API. From the point of view of using a stable library, I’m not sure… possibly the default should be to warn about usage of such items (note that before any items are marked stable the library is still considered unstable so this wouldn’t cause mass warnings, provided that when stability attributes are added to the library they are added to all public items at once).

RFC 1270 already specifies that the since field for #[deprecated] is semver.

The only difference is that rustc enforces this constraint instead of letting external tools handle it.

A good suggestion! I've updated the text accordingly.

When compiling a stable crate, absolutely, and it should become a hard error eventually. But I don't think using an unspecified item should be a warning, because right now if you're using a stable crate, you'd expect items that aren't deprecated to be stable. (That was actually my reasoning behind making items in a stable crate stable by default, but I think unspecified is the superior solution here.)

I like the general idea of this RFC, but I’m not sure the detailed design is correct. Most significantly, I’m uncomfortable with the language having a notion of semantic versioning.

I’m more in favor of something similar to the conditional dependencies RFC, in which the bulk of the work is handled by cargo telling rustc which attributes to turn on / off during compilation. I’m not sure if that’s workable, but I’d like it to be explored.

I also am unclear on how this interacts with the deprecated tag.

Now that you mention it, yeah, we probably don't want the language itself being semver-aware. The crate version stuff can be enforced by Cargo, but I'm not sure how we can deal with multiple stability attributes without a versioning scheme to order them by. I guess that brings us back to only allowing one stability attribute per item?

I looked at that RFC, but I'm not exactly sure what you mean by this. Could you explain in more detail?

Long story short: if an item is #[deprecated], it's now recognised as being unstable in addition to being recognised as deprecated (but rustc only warns about the item being deprecated). I agree that the text should be clearer about this.

rustc has some way to be told which versions should be considered stable, and cargo passes rustc that information based on its knowledge of semver and what version this crate is.

I don't know a workable solution that allows rustc to stick to string equality matching on the since field, since e.g. if you have version 1.1.0, that is "since" an infinite number of versions from 1.0.0, 1.0.1, 1.0.2....

I'm not really sure what the motivation is for supporting multiple stability attributes. If stability were instead a boolean state, and the "since" flag were just documentation, this wouldn't be an issue.

A more layered solution might be this:

If cargo determines that a crate is “stable,” it passes a “stable” feature when compiling it, in which case rustc only compiles items tagged “stable.” The since flag on stable and the unstable attribute are just documentation for users.

From this basis you add cargo features to allow users of a stable crate to opt into its unstable API and warnings and such as might be appropriate.

Features are supposed to be additive, so a feature to disable parts of a crate doesn’t make much sense. I like the idea of not building unstable items at all, though! I’m currently imagining something like this:

  • rustc gains awareness of item stability. Up to one stability attribute per item, and the fields are just documentation.
  • Everything that belongs to a top-level crate is built no matter what (otherwise main() would have to be #[stable], for example).
  • By default, dependencies only have their stable and unspecified items built. Rust gains a new extern crate attribute, #[unstable_use], which is basically #[macro_use] but for unstable items.
  • rustc does not gain awareness of crate stability. Instead, Cargo is responsible for ensuring that >= 1.0.0 crates are sound (i.e. contain either no publically-visible items, or at least one publically-visible stable item; eventually unspecified items will be banned as well).

Thoughts?

This makes sense: deprecated items are just unstable with documentation to say why (perhaps including "replaced by" or "see also"... only it might be better to do this with free-form doc than an attribute).

The other point here is that the information could be contradictory: rustc can't download the marked version and check it's correct. Making the field purely documentation makes it clearer that the field should be interpreted by a human.

No it shouldn't become a hard error. This stuff comes up from time to time, and it's important: warnings are there to help humans avoid mistakes and hard errors are there to help machines avoid mistakes. Get that wrong and you start finding old code that used to work just broke because of some new lint in the latest tools which tells you in effect no you can't just run your old tried and tested code because it doesn't conform to our latest conventions.

Couple of thoughts, sorry for the scattershot reply but there's not a common thread between them.

Features are supposed to be additive, so a feature to disable parts of a crate doesn't make much sense

By "feature" I just mean some flag for conditional compilation. Also, features can totally be used to disable compilation through #[cfg(not())] attributes, but that's neither here nor there because I'm imagining a more native support than 'just' using the feature system.

Everything that belongs to a top-level crate is built no matter what (otherwise main() would have to be #[stable], for example).

If you feel that main needs to always be built, Rust already has an internal notion of the "main" function, and you could just always build that item. Though with #[cfg(not())], you could conditionally fail to compile main today & I don't see that as an inherent problem. More importantly, I don't think this attribute should be aware of which module it is in.

I don't see why a binary would ever be compiled with the "stable only" feature activated, but I think that ties into my next comment.

By default, dependencies only have their stable and unspecified items built.

There are a few different definitions of "by default" - what rustc does by default and what cargo does by default.

I think, by default, rustc should ignore these attributes. But rustc can accept flags which control how it interprets stable/unstable/absent attributes.

I think there are a couple of different valid default options for cargo. What you've said is one of them, but here's another (I've no strong opinion about which is better):

cargo does not pass any argument to rustc by default; by default this system is ignored. A crate's Cargo.toml, though, can have an attribute indicating that it is using stability attributes; if it has that attribute that means that when compiling it as a dependency, only stable items should be compiled (not untagged items).

Rust gains a new extern crate attribute, #[unstable_use], which is basically #[macro_use] but for unstable items.

I'd much rather this be an attribute you add to your declaration of that dependency in Cargo.toml, not an attribute to the extern crate declaration. But the principle is the same.

rustc does not gain awareness of crate stability. Instead, Cargo is responsible for ensuring that >= 1.0.0 crates are sound (i.e. contain either no publically-visible items, or at least one publically-visible stable item; eventually unspecified items will be banned as well).

I don't like this part of the RFC at all - I don't think crates should be compelled to use this system. I'd much rather the situation be that crates can choose to use this system, in which untagged items are treated as unstable.

Long story short: if an item is #[deprecated], it's now recognised as being unstable in addition to being recognised as deprecated (but rustc only warns about the item being deprecated). I agree that the text should be clearer about this.

This isn't consistent with how deprecation works in std. In std, if an item is deprecated, it is still "stable," it just emits a deprecation warning.

Two thoughts jump out at me when first reading the RFC.

  1. All items/crates should be implicitly unstable.
  2. Replace the unstable attribute with experimental.

I really liked the experimental attribute (back when it was usable) it gives the same kind of notion of unstable, but with a little hint that it’s moving towards stability. I would think of it as a staging level of stability. This would avoid the currently confusing distinction between undefined and unstable IMO.

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