Idea: keyword gate annotation #![keywords(...)]


#1

During the throw/fail keyword reservation RFC, @aturon proposed an alternative to keyword reservation that was, later, enhanced by sfackler. I’ve restated the idea below.


Motivation

Rust editions present an opportunity to reserve keywords and other syntactic constructs, and in the 2018 edition, several such reservations were proposed, including keyword reservations for exception propagation, trait delegation, named arguments, and the ! type. The goal of these reservations was to decrease the pressure to propose a full RFC by reserving these constructs proactively, deferring further discussion until after Rust 2018 shipped. As form follows function, the syntax reservation RFCs included discussion of the feature at large, now with heightened urgency imposed by the edition. This created precisely the situation keyword reservation intended to avoid.

Keyword gates alleviate this pressure by allowing keyword reservation behind a specialized feature gate. Gated keywords are then promoted out of the gate in the next edition.

High-level explanation

Keyword gates are a meta-feature. The guide-level explanation applies to specific keywords. The following text demonstrates a guide-level explanation for a hypothetical keyword, yolo:

In order to prevent unexpected name clashes with yolo, you have to activate the keyword. This example activates yolo within a module:

#[keywords(yolo)]
mod foo {
// ...
}

You can enable yolo for a whole crate by adding #![keywords(yolo)] to the crate’s lib.rs.


The two comments I linked above provide some additional context to the idea, notably @aturon’s idea of limiting the scope of the annotation by strictly tying it to the edition cycle. est31, joshtriplett, warlord500, withoutboats, steveklabnik, centril, Ixrec, and I (illustrious-you) also offered their opinions during the throw/fail FCP.

I suggest reading those comments to regain the context I didn’t cover in the strawman proposal above.

Thoughts?


#2

My comment from that thread is still relevant:


#3

My apologies, Steve. In my haste to write this up, I didn’t include the feedback you and others provided in the throw/fail PR. I updated the FP with links to each person’s initial response to the idea.

This was one of the original models we proposed for editions generally, but was rejected. What’s changed since then? It’d be worth re-exploring.

I agree we should explore this more deeply. The motivation section, above, provides the seed for such a discussion: Keyword reservation is supposed to be a way to defer argument, but we ended up having the argument anyway, with greater time pressure.

I’m also very wary of doing this now.

To be clear: I don’t know if I want this feature at all, and I certainly don’t want to add it now. What I’m after is twofold:

  • seeding the discussion, so that if there’s a need to revise the policy, it can incorporate the 2018 edition’s lived experience.
  • providing a topic to discuss keyword reservation policy, so that threads like Reserve `never` for 2018 epoch can focus on the technical merits.

In service of those ends, the above idea is intentionally light on the details.


#4

Oh it’s all good! I wouldn’t expect you to have summarized everything. Just wanted to make sure it didn’t get lost!

seems great :+1:


#5

To respond to @steveklabnik’s point:

The model that we rejected was feature flags on stable. I do not think this is the same thing, for a few reasons:

  • The fear around feature-flags-on-stable was that we would have diverging “dialects” of Rust (this project uses features X, Y, and Z, but not W).
  • The fact that we have an edition coming along every few years means that we “consolidate” these keywords flags. So once Rust 2021 comes along, all the keywords will become available.
  • Plus, importantly, this is limited to keywords and not other sorts of features.

I kind of like the idea of issuing “minor editions” every so often, so you might have edition = "2018.1" or something, which adds a few keywords but isn’t a “big event” (unlike Rust 2021).


#6

Sorry, I don’t think this quite covers it :slight_smile:

Isn’t this the same, just at a smaller scope? Maybe that smaller scope is what makes it okay, but now we’ll have code where something is a keyword and code where it’s not.

This was also part of the general features on stable proposal; opting into an edition is basically opting into a particular set of feature flags.

Note that all of these flags would still have to be supported by the compiler, forever, at the appropriate edition, so while it may be simpler for users, it doesn’t hide the complexity in the compiler in any way. Instead of being per-edition, it’s now per edition modulo a flag.

Why is that significant? The mechanism is the same. I guess, as I mentioned above, it could be solely at a smaller scope?


#7

In short, many new features don’t require new keywords. Look back at the list of big features we are pushing on now: very few of them, if any, demand a new keyword. It also implies that the features cannot have “deep semantic changes” (in the same way that an edition cannot introduce such changes) – everything must desugar to a common core.

So for example: if we had feature gates on stable, then things like match-default-bindings (no keyword), use crate::foo::bar paths (old keyword, new use), and so forth might become opt-in. I’d prefer that not to be the case, because that really starts to feel like “subsets of Rust”.

I agree that this is a version of the features-gates-on-stable proposal: just a relatively weakened one, and with the opposite emphasis. The real transition mechanism is editions. But between editions, we have this mechanism to let you get a temporary “preview” of what is to come.

Overall, I see basically five ways forward when it comes to keywords. Here are the pros/cons I see in short:

  • Use some less good syntax in the interim. e.g., do try { ... } instead of try { .. }.
    • Pro: keywords available to all immediately, easy to migrate to new edition
    • Con: Looks funny, might not always work?
  • Use #![keywords(foo)] to opt-in on a limited basis.
    • Pro: easy to implement, sort of easy to explain
    • Con: lets people subset the language in the middle of an edition
  • Minor-editions (2018.1)
    • Pro: keeps the language moving forward in lock step in some sense
    • Con: seems like a lot of machinery, confuses the messaging in some sense
  • Reserve keywords
    • Pro: have the syntax you want…
    • Con: …if you can predict and agree upon it in advance (i.e., puts the bikeshed first and foremost, before we’ve even fully designed the feature!). Also, if we reserve multiple keywords, causes more pain than will be needed.
  • Wait it out
    • Pro: simple
    • Con: long time to wait sometimes!

Are there more possibilities?


#8

Cool, thanks. So it does seem that scope is the significant bit here.

I’m still a fan of “Reserve keywords” personally.


#9

Have a use-site marker for something being a keyword, like we have r#loop to make something an ident instead of a keyword. Has a bonus of making it possible to write code that works both before and after the edition that reserves it, and is a localized thing, not a crate-level attribute.

This is a big part of why I think the “just wait for the next train” that works well for our regular releases cannot work for editions. And it’s even worse because it’s not only long, it’s uncertain. I’ve heard 2021 a bunch, but I’ve also heard shorter and longer.


#10

I’d be concerned that this may be confused with versions. Using arbitrary identifiers: Someone may try to unlock “2018.3”, then wonder why it hasn’t worked, only to find they need both that and the Rust 1.23 compiler.

It might also be confusing if the minor edition gates keywords but not other syntax changes (e.g. elisions).

There was a post on one of the RFCs (I do not remember which) from one of the serde(?) maintainers about syntax and backwards compatibility. They said, in effect, that they’re careful to upgrade to new compilers only when they can improve their API, specifically to avoid accidentally relying on ergonomic features. Minor editions may meet their needs better, since they’d be able to get the bugfixes and performance improvements of a new compiler without worrying about accidentally relying on those features.

On the other hand, I’d expect that to spike front-end complexity, as the minor editions could never be eliminated, and it butchers the epoch RFC’s guidance:

When opting in to a new epoch, existing deprecations may turn into hard errors, and the compiler may take advantage of that fact to repurpose existing usage, e.g. by introducing a new keyword. This is the only kind of breaking change a epoch opt-in can make.

If keyword gates are used, there should also be a “warn” level lint released ahead of the next edition to prepare for eliminating the keyword gate. This, then, implies that any keywords added after the “lint window” must be gated–they shouldn’t be included in the edition, because, otherwise, there wouldn’t be enough downstream notice.

Is there a variant of this that leverages a new rustup channel? I’m imagining something after “stable”, with the only difference being keyword reservation. When a word loses a meaning In linguistics, it’s called “archaic”. It has a negative connotation when applied to computer programs, but it’s as good a word as any to explain the channel’s motive.


#11

Contextual keywords, not always feasible, but it was done for union keyword.


#12

Specifically, not possible for try, fail, throw and such cases.


#13

Fair enough, I suppose the alternative to fail and throw could be to use macro syntax, which is yet another possibility for the list (in fact, that’s what failure crate already does with bail! macro). This isn’t feasible for try for obvious reasons, although those situations should be rare.