Gating API on editions

Idea 1: pub (..edition)

pub (..2018) fn foo() {..}
pub (2021..) fn bar() {..}

would a restriction on which items are considered public depending on edition solve this?

Idea 2: std features

editions => features in std-aware cargo?

Concern: 2018 and 202a crates shouldn't see conflicting impl-s of a trait, etc. But these issues should already exist when features drive conditional compilation today..

Update

This is a spin-off from 2021 edition topic; one of the ideas discussed was if a hypothetical 202a edition could not only remove deprecated syntax but also deprecated API-s.

Indeed there is a lot less motivation to have this outside of std. As suggested bellow new syntax can easily be replaced by unstable attributes.

Whichever syntax used the idea was to discuss a possible implementation path: either tweaks to visibility (pub) or conditional compilation in std-aware cargo.

Apologies for initial excessive brevity and lack of clarity.

What about something based on configurations instead?

#[cfg(edition="..2018")] pub fn foo() {..};
#[cfg(edition="2020..")] pub fn bar() {..};

You could also use this to create chains of 'if-else' statements, which will let you panic if the edition is unexpected.

1 Like

That's almost exactly what I meant by "Idea 2" above

Who would use this feature? The only "customer" I can think of is the stdlib, and even then I'm not 100% convinced this is useful (because the stdlib still has to support all editions).

2 Likes

I see, I didn't understand what you meant.

Yes this is an attempt to design a mechanism to allow API-s to be "removed" from STD in future editions

This was born from the discussion on 2021 edition?, which @atagunov referenced by his quote of @CAD97. That discussions seems to have spawned some new ideas, and (I think) @atagunov realized that since we were moving away from the topics subject in 2021 edition? that it would be better to create a new topic (@atagunov, please correct me if I'm wrong).

1 Like

We couldn't actually remove the APIs, since we still support the old editions. And if you're already compiling std yourself along with your program, then Rust doesn't need any special support to omit code that your program doesn't call.

That said, I could see something like this helping us if we want to hide old deprecated APIs and prevent using them from new editions.

1 Like

That's exactly the point

I'm not seeing how this would help any real-world use cases, unless it's subtly violating the restrictions on edition changes that we need to keep all the editions mutually compatible.

In particular, I doubt we can use this for any trait implementations because that would affect coherence, and in general I think it's ambigous what "the user" of an impl actually is when you consider non-trivial cases like the classic "HashMap problem" with multiple crates and layers of generic code between std and the "user" (does anyone here grok trait/type systems well enough to confirm that?).

Plus, every new std API is already gated on whatever rustc version added it, just like editions are. Do we have any examples of std APIs (that aren't trait impls) that we'd rather gate on an edition than a rustc version? Or where it would matter at all?

3 Likes

True, old impl-s stay and coherence still cannot be broken

  • so currently using std API X is prohibited if MSRV < X_startVersion
  • the suggestion is to also prohibit it if edition > X_endEdition

This is a tool to hide deprecated parts of API. It's indeed limited in power

For a hypothetical example, take std::alloc::alloc. The docs say:

This function is expected to be deprecated in favor of the alloc method of the Global type when it and the Alloc trait become stable.

With edition gating, Rust circa 2030 could hide it from any code using the 2030 edition or newer, so that calling it is a hard error.

Perhaps more substantially there's std::mem::uninitialized which "basically cannot be used correctly".


I don't know if this edition gating is a good idea or not but I think the reasoning is it can help keep the std from one day looking like a Rust history lesson (landmines included). Instead it would present a modern, clean interface to modern code.

Compatibility between editions will be preserved because it only pretends to remove deprecated interfaces. They would still be there. And old APIs would be internally re-written in terms of more modern ones where that's possible (this already happens, no?).

How this would be implemented in practice, I don't know.

3 Likes

Ah, okay, if it's only for pseudo-removals then this makes sense to me. Since all of this only applies to std, it'd almost certainly be some kind of perma-unstable #[rustc_hide_on_edition(2021)] attribute so there's no need to design a "proper syntax" like the pub(..2021) suggested above (that's probably part of what threw me off).

The only real challenge there is what do we do with the docs pages. Can we make pseudo-removed APIs hidden enough that we really get the cleanup benefits, without making them so hidden that people start failing to locate docs for stuff they run across in existing code? I'm not sure.

3 Likes

Why not add a rust_2018 cargo feature to your crate which does this gating?

@ckaran @atagunov I'm familiar with the ongoing discussion of the 2021 edition. My point in asking "who would use this feature?" is that if the stdlib is the only user of this feature, then it doesn't need an RFC, syntax change, or new (publicly usable) attribute. The stdlib already uses internal-only macros. They could easily add another one that signals edition-related information.

The hard part isn't "what syntax should we use?" (because if stdlib is the only customer the answer is trivial: use a new custom attribute). The hard part is actually getting this to work at all because of the machinery involved.

To put it bluntly (with apologies), I'm not sure what the point of this discussion is if there aren't use cases for this feature outside of the stdlib.

2 Likes

If the only same use-case for this is the standard library, why does there have to be special syntax or some other construct? There could be something wholly confined to std, the details of which doesn't much matter.

Hi, the above is actually an attempt to sketch an implementation path:

  • tweaks to visibility (as if it was a different kind of pub)
  • std-aware cargo + conditional compilation (similar to how features work)

This is a spin-off from 2021 edition topic; one of the ideas discussed was if a hypothetical 202a edition could not only remove deprecated syntax but also deprecated API-s. Sorry, should have been more clear about this; added an Update to the first post.

Indeed there is a lot less motivation to have this outside of std.

Which is (unfortunately) a long ways off, definitely not within the "edition 202a" timeline.

Plus, "just use features" doesn't work here, because one std has to serve every edition simultaneously.

All the involved parties know roughly how edition based visibility would look; add a rustc_hidden_after_edition attribute to #[deprecated] (or outside of it, w/e), and then have editions after that treat the deprecation as a hard error instead of a warning. That part isn't actually that hard.

The hard part is primarily the documentation story. How do you actually benefit from cleaning up the API surface without making functionality that is still there in older editions effectively undocumented? Most people will use the online rust-lang docs, not some local copy (which still isn't attached to a workspace anyway (which doesn't even have to use a singular edition)).

This is probably a solveáble problem, just not a solvéd one.

2 Likes

Now that looks easy :slight_smile: a doc page could consist of

  • items visible in latest edition
  • <hr/>
  • title: removed in edition 2021
  • "removed" items

Color/background/strikethrough/etc can be used to differentiate "removed" items from normal ones. There can possibly be a clickable button to switch the page into an "old edition" mode

I guess the thinking was the set of necessary editions would be collected from all crates constituting project thus pulling in exactly the portion of std that's needed.

1 Like

I have a different point which has to do with the idea of gating API on editions, something I thought of when recently reading about problems that could/would arise when adding implementations for IntoIter (as an iterator by value) to array-types.

This plays into the point that API-gating couldn’t really apply to trait impls as you can’t have a type implement a trait in one place but not implement the trait in another place, which is true.

The actual problem however was that calls of x.into_iter() for an x: [SomeType; N] currently auto-refs and call into_iter() on &x creating an iterator by-reference, so that adding the impl for the array can change the meaning of existing code. My idea here: Why not gate trait implementations on editions just for the sake of method resolution?

A method call expression would take into consideration at what edition the code it comes from is, and then it only takes impls that are in that edition into consideration. This would help avoid breaking existing code. It does what editions are for: you cannot actually change the language or the std lib in a not backwards-compatible way, but you can change how new code is interpreted syntactically; making deprecated items “invisible” (i.e. hard errors) fits just as well into this category.

1 Like