From 4b1c9100d06625d94b287e46fd58cb72c90f46d7 Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 12 Sep 2025 15:23:09 +0100 Subject: [PATCH 1/2] RFC for `#[stable(since)]` --- text/0000-stable_since.md | 81 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 text/0000-stable_since.md diff --git a/text/0000-stable_since.md b/text/0000-stable_since.md new file mode 100644 index 00000000000..d167ea9e243 --- /dev/null +++ b/text/0000-stable_since.md @@ -0,0 +1,81 @@ +- Feature Name: stable_since +- Start Date: 2025-09-12 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#74182](https://github.com/rust-lang/rust/issues/74182) + +# Summary +[summary]: #summary + +Allow crates to specify `#[stable(since = "version")]` on items for Rustdoc to render the information. + +# Motivation +[motivation]: #motivation + +This functionality is already implemented for the standard library. Other crates also expand their public API over time, and it would be helpful to inform users about the minimum crate version required for each item. + +It's possible to automatically infer in which version each item has been added by comparing public APIs of crates' public releases, but this is too expensive and complicated to perform each time when generating documentation. The `#[stable(since)]` attribute behaves like a first-class cache for this information for `rustdoc` and other tools. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +When added to an item, it specifies in which version of the crate this item has been added: + +```rust +#[stable(since = "2.25.0")] +pub fn add_trombone_emoji() {} +``` + +Rustdoc will include the version in the documentation of the item, with a description such as "stable since $crate_name version 2.25.0". + +To ease development of unreleased features, there is no restriction on the version range, and it may refer to a future not-yet-released version. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The `version` must parse as SemVer. It refers to a version of the crate that defines the item this attribute belongs to. + +`rustc` doesn't need to be made aware of crates' versions, because there's no restriction on the version range. + +`rustdoc` should not display the attribute on items re-exported from other crates. + +# Drawbacks +[drawbacks]: #drawbacks + +This attribute may be incorrect if added manually. It may be confused with rustc version compatibility. + +It specifies only a single version per item, which may not be enough to fully explain availability of items that are available conditionally or under different paths. + +Versions on re-exported items are not relevant for the crate re-exporting them, because it matters when the re-export has been added. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +- The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates +- API stability could be stored outside of the source code, e.g. in a file similar to `rustdoc`'s JSON +- It could be shortened to `#[since("version")]` +- It could be expanded to `#[stable(added = "version", changed = "version", rust_version = "msrv")]` + +# Prior art +[prior-art]: #prior-art + +The `#[stable(since = "version")]` attribute is a subset of standard library's `#[stable(feature, since)]`, but to keep the scope small, this RFC does not include support for feature flags nor unstable APIs outside of the standard library. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +Should crates reset the `version` when making semver-breaking changes to the item? + +Should the `version` allow a placeholder value like `UNRELEASED`? + +Is it clear enough that the version is the crate's own version and not the minimum requierd Rust version? + +How to support items re-exported from other crates? Could `use` support overriding `#[stable(since)]`? + +# Future possibilities +[future-possibilities]: #future-possibilities + +The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates. + +Tools like rust-analyzer or clippy could help users bump versions in `Cargo.toml` when their crate uses items from a newer version of a dependency than the minimum version specified in `Cargo.toml`. + +These attributes could be automatically generated by tools like `cargo-public-api` or `cargo-semver-checks`. From 0a4b7f557db267e9dd2c569ef38237503cbca25f Mon Sep 17 00:00:00 2001 From: Kornel Date: Fri, 12 Sep 2025 15:23:09 +0100 Subject: [PATCH 2/2] RFC for `#[stable(since)]` --- text/0000-stable_since.md | 52 ++++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/text/0000-stable_since.md b/text/0000-stable_since.md index d167ea9e243..6fef32d4300 100644 --- a/text/0000-stable_since.md +++ b/text/0000-stable_since.md @@ -13,12 +13,12 @@ Allow crates to specify `#[stable(since = "version")]` on items for Rustdoc to r This functionality is already implemented for the standard library. Other crates also expand their public API over time, and it would be helpful to inform users about the minimum crate version required for each item. -It's possible to automatically infer in which version each item has been added by comparing public APIs of crates' public releases, but this is too expensive and complicated to perform each time when generating documentation. The `#[stable(since)]` attribute behaves like a first-class cache for this information for `rustdoc` and other tools. +It's possible to automatically infer in which version each item has been added/changed by comparing public APIs of crates' public releases, but this is too expensive and complicated to perform each time when generating documentation. The `#[stable(since)]` attribute can provide this information in a way that is readily available for `rustdoc` and other tools. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -When added to an item, it specifies in which version of the crate this item has been added: +When added to an item, it specifies the oldest version of the crate that has this item available, and implemented in a way that is compatible with the crate's current version: ```rust #[stable(since = "2.25.0")] @@ -29,19 +29,27 @@ Rustdoc will include the version in the documentation of the item, with a descri To ease development of unreleased features, there is no restriction on the version range, and it may refer to a future not-yet-released version. +The version in `since` must be updated when the interface changes in a semver-incompatible way. + +The version in `since` should be updated when the behavior of the item changes significantly. If in doubt, it should be the later version. Dependency management tools may use this version to suggest bumping miniumum required version in `Cargo.toml`. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -The `version` must parse as SemVer. It refers to a version of the crate that defines the item this attribute belongs to. +The version in `since` refers to a version of the crate that defines the item this attribute belongs to. The version should parse as Cargo-compatible SemVer, but this won't be enforced by `rustc`, only `clippy` (same as `#[deprecated]`). + +`rustdoc` should not display the attribute on items defined in other crates. -`rustc` doesn't need to be made aware of crates' versions, because there's no restriction on the version range. +This attribute on `pub use` refers to availability of the path. -`rustdoc` should not display the attribute on items re-exported from other crates. +This attribute may be set on public items that are not accessible outside of the crate. This attribute set on private items may trigger a warning. # Drawbacks [drawbacks]: #drawbacks -This attribute may be incorrect if added manually. It may be confused with rustc version compatibility. +It's not obvious how this attribute should be used with APIs that change over time. It's simpler to only specify when an item has been added, but this can be misleading if the latest version differs significantly from the earliest version. Specifying the lowest *compatible* version is more useful for choosing the minimum required crate version, but this type of compatibility is hard to define (semver is well understood when upgrading, but downgrading loses new features and reintroduces old bugs, so even a semver-patch downgrade may be breaking). + +This attribute may be incorrect if added manually, or become stale if not updated after a significant change. It specifies only a single version per item, which may not be enough to fully explain availability of items that are available conditionally or under different paths. @@ -50,32 +58,42 @@ Versions on re-exported items are not relevant for the crate re-exporting them, # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -- The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates -- API stability could be stored outside of the source code, e.g. in a file similar to `rustdoc`'s JSON -- It could be shortened to `#[since("version")]` -- It could be expanded to `#[stable(added = "version", changed = "version", rust_version = "msrv")]` +The version used in third party crates is unlikely to be confused with MSRV, because very few crates have only versions in the 1.xx range relevant for Rust versions. + +`since` is used for consistency with `#[deprecated(since)]`. + +Alternatives: + +- The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates. +- API stability could be stored outside of the source code, e.g. in a file similar to `rustdoc`'s JSON. +- It could be generalized to `#[changed(version = "semver", note = "how")]` that provides a changelog for each item. This could also be implemented by allowing multiple instances of `#[stable(since = "version", note = "changes")]`. +- docs.rs could generate `rustdoc` JSON for all crates.io crates, making it easier to diff releases and annotate API changes automatically. # Prior art [prior-art]: #prior-art -The `#[stable(since = "version")]` attribute is a subset of standard library's `#[stable(feature, since)]`, but to keep the scope small, this RFC does not include support for feature flags nor unstable APIs outside of the standard library. +The `#[stable(since = "version")]` attribute is a subset of standard library's `#[stable(feature, since)]`. This RFC does not include support for feature flags nor unstable APIs outside of the standard library. The standard library has unusual backwards-compatibility constraints, so it doesn't serve as a guide for hanlding APIs with breaking changes. # Unresolved questions [unresolved-questions]: #unresolved-questions -Should crates reset the `version` when making semver-breaking changes to the item? - -Should the `version` allow a placeholder value like `UNRELEASED`? +Should it specify the oldest version regardless of compatibility? Should it be bumped only on semver-breaking API changes, or also when the behavior changes? -Is it clear enough that the version is the crate's own version and not the minimum requierd Rust version? +Should it be allowed on private items? (there's `--document-private-items`, but those items won't be accessible from outside of the crate). -How to support items re-exported from other crates? Could `use` support overriding `#[stable(since)]`? +Should it support specifying different kinds of stability, like `const_stable`? # Future possibilities [future-possibilities]: #future-possibilities The entire `#[stable(feature)]`/`#[unstable(feature)]` functionality could be stabilized for 3rd party crates. -Tools like rust-analyzer or clippy could help users bump versions in `Cargo.toml` when their crate uses items from a newer version of a dependency than the minimum version specified in `Cargo.toml`. +A placeholder like `NEXT` or `UNRELEASED` could be supported, and either automatically updated or rejected by `cargo publish` (clippy allows "TBD" in `deprecated(since)`). + +A new `#[changed]` attribute could be added for tracking history of incompatible changes or extensions. This RFC proposes `#[stable]` to track the latest incompatible change, so `#[changed]` would provide information about partial compatibility with older versions, or a changelog. + +Tools like rust-analyzer or clippy could help users bump versions in `Cargo.toml` when their crate uses items from a newer version of a dependency than the minimum version specified in `Cargo.toml` (like `clippy::incompatible_msrv` for crates). These attributes could be automatically generated by tools like `cargo-public-api` or `cargo-semver-checks`. + +These attributes could be used by tools that auto-generate changelogs.