rust-lang / rust-lang/rust

Unstable attribute behavior on re-exports is surprising

Open
#161,153 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-stability A-visibility T-compiler T-lang T-libs T-rustdoc
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

Currently, #[unstable] on re-exports (both direct and glob ones) appears to be ignored.

This caused an issue identified in https://github.com/rust-lang/rust/issues/159856 where core::derive was unintentionally stabilized as a result of the unintuitive and non-obvious current behavior here.

This is the cause of further unintuitive (and I'd argue undesirable) behaviors like the following:

#![crate_type = "lib"]
#![crate_name = "unstable_example"]
#![allow(internal_features, unused_features)]
#![feature(staged_api, unstable_test_feature)]
#![stable(feature = "example", since = "1.0.0")]

mod implementation {
    #[stable(feature = "example", since = "1.0.0")]
    pub struct Example;
}

mod intermediate {
    #[unstable(feature = "unstable_test_feature", issue = "none")]
    pub use super::implementation::*;
}

#[stable(feature = "example", since = "1.0.0")]
pub use intermediate::*;

Here, unstable_example::Example is considered stable. This means that the unstable glob re-export inside mod intermediate is load-bearing for stability and non-breakage. That doesn't seem set up for success!

@nia-e and I bounced some ideas around, and I'd like to pitch a path forward based on what we discussed. Here's the guiding principle:

To the maximum extent possible, removing or altering #[unstable] code should not cause stable breakage.

Within a single crate, a given path's stability is resolved by (i.e., as-if) walking the path components, checking each of them for stability, and checking whether the name of that component was brought into scope by an unstable re-export. Specifically:

  • names that exist in a namespace as a result of an #[unstable] re-export are themselves considered unstable; any further #[stable] re-exports of such an unstably-re-exported item are considered unstable
  • if an item is re-exported into a namespace more than once and at least one of those re-exports is #[stable], the re-export is considered stable — even if the actual #[stable] re-export is merely a glob while the unstable re-export is direct and normally would have precedence

This path-dependent stability analysis ends at crate boundaries. If one crate wants to stably re-export another crate's unstable items, it can do so — meaning e.g. core::io::Error can remain unstable while std::io::Error is still a stable re-export.

This scheme is designed to be 1-1 compatible with how we (should) treat #[doc(hidden)]. Just swap #[unstable] for #[doc(hidden)] and the same guiding principle and rules apply. This would include the "ends at crate boundaries" rule, so that a maintainer of multiple crates can choose to define non-public-API functionality in one and only commit to it in public API in another.

I believe this to be an intuitive and sensible interpretation of the goals that #[stable], #[unstable], and #[doc(hidden)] set out to achieve. I hope you agree! cargo-semver-checks can enforce this interpretation as well, of course.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

No source files, tests, or compiler entry points are named. Start by locating the stability handling for direct and glob re-exports, then trace how path components and unstable re-exports are analyzed within a crate. Done means the proposed path-dependent rules are implemented and the core::derive regression and examples in this issue behave as intended.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.