Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stabilize opaque type precise capturing (RFC 3617) #127672

Merged
merged 2 commits into from
Aug 20, 2024

Conversation

compiler-errors
Copy link
Member

@compiler-errors compiler-errors commented Jul 13, 2024

This PR partially stabilizes opaque type precise capturing, which was specified in RFC 3617, and whose syntax was amended by FCP in #125836.

This feature, as stabilized here, gives us a way to explicitly specify the generic lifetime parameters that an RPIT-like opaque type captures. This solves the problem of overcapturing, for lifetime parameters in these opaque types, and will allow the Lifetime Capture Rules 2024 (RFC 3498) to be fully stabilized for RPIT in Rust 2024.

What are we stabilizing?

This PR stabilizes the use of a use<'a, T> bound in return-position impl Trait opaque types. Such a bound fully specifies the set of generic parameters captured by the RPIT opaque type, entirely overriding the implicit default behavior. E.g.:

fn does_not_capture<'a, 'b>() -> impl Sized + use<'a> {}
//                               ~~~~~~~~~~~~~~~~~~~~
//                This RPIT opaque type does not capture `'b`.

The way we would suggest thinking of impl Trait types without an explicit use<..> bound is that the use<..> bound has been elided, and that the bound is filled in automatically by the compiler according to the edition-specific capture rules.

All non-'static lifetime parameters, named (i.e. non-APIT) type parameters, and const parameters in scope are valid to name, including an elided lifetime if such a lifetime would also be valid in an outlives bound, e.g.:

fn elided(x: &u8) -> impl Sized + use<'_> { x }

Lifetimes must be listed before type and const parameters, but otherwise the ordering is not relevant to the use<..> bound. Captured parameters may not be duplicated. For now, only one use<..> bound may appear in a bounds list. It may appear anywhere within the bounds list.

How does this differ from the RFC?

This stabilization differs from the RFC in one respect: the RFC originally specified use<'a, T> as syntactically part of the RPIT type itself, e.g.:

fn capture<'a>() -> impl use<'a> Sized {}

However, settling on the final syntax was left as an open question. T-lang later decided via FCP in #125836 to treat use<..> as a syntactic bound instead, e.g.:

fn capture<'a>() -> impl Sized + use<'a> {}

What aren't we stabilizing?

The key goal of this PR is to stabilize the parts of precise capturing that are needed to enable the migration to Rust 2024.

There are some capabilities of precise capturing that the RFC specifies but that we're not stabilizing here, as these require further work on the type system. We hope to lift these limitations later.

The limitations that are part of this PR were specified in the RFC's stabilization strategy.

Not capturing type or const parameters

The RFC addresses the overcapturing of type and const parameters; that is, it allows for them to not be captured in opaque types. We're not stabilizing that in this PR. Since all in scope generic type and const parameters are implicitly captured in all editions, this is not needed for the migration to Rust 2024.

For now, when using use<..>, all in scope type and const parameters must be nameable (i.e., APIT cannot be used) and included as arguments. For example, this is an error because T is in scope and not included as an argument:

fn test<T>() -> impl Sized + use<> {}
//~^ ERROR `impl Trait` must mention all type parameters in scope in `use<...>`

This is due to certain current limitations in the type system related to how generic parameters are represented as captured (i.e. bivariance) and how inference operates.

We hope to relax this in the future, and this stabilization is forward compatible with doing so.

Precise capturing for return-position impl Trait in trait (RPITIT)

The RFC specifies precise capturing for RPITIT. We're not stabilizing that in this PR. Since RPITIT already adheres to the Lifetime Capture Rules 2024, this isn't needed for the migration to Rust 2024.

The effect of this is that the anonymous associated types created by RPITITs must continue to capture all of the lifetime parameters in scope, e.g.:

trait Foo<'a> {
    fn test() -> impl Sized + use<Self>;
    //~^ ERROR `use<...>` precise capturing syntax is currently not allowed in return-position `impl Trait` in traits
}

To allow this involves a meaningful amount of type system work related to adding variance to GATs or reworking how generics are represented in RPITITs. We plan to do this work separately from the stabilization. See:

Supporting precise capturing for RPITIT will also require us to implement a new algorithm for detecting refining capture behavior. This may involve looking through type parameters to detect cases where the impl Trait type in an implementation captures fewer lifetimes than the corresponding RPITIT in the trait definition, e.g.:

trait Foo {
    fn rpit() -> impl Sized + use<Self>;
}

impl<'a> Foo for &'a () {
    // This is "refining" due to not capturing `'a` which
    // is implied by the trait's `use<Self>`.
    fn rpit() -> impl Sized + use<>;

    // This is not "refining".
    fn rpit() -> impl Sized + use<'a>;
}

This stabilization is forward compatible with adding support for this later.

The technical details

This bound is purely syntactical and does not lower to a Clause in the type system. For the purposes of the type system (and for the types team's curiosity regarding this stabilization), we have no current need to represent this as a ClauseKind.

Since opaques already capture a variable set of lifetimes depending on edition and their syntactical position (e.g. RPIT vs RPITIT), a use<..> bound is just a way to explicitly rather than implicitly specify that set of lifetimes, and this only affects opaque type lowering from AST to HIR.

FCP plan

While there's much discussion of the type system here, the feature in this PR is implemented internally as a transformation that happens before lowering to the type system layer. We already support impl Trait types partially capturing the in scope lifetimes; we just currently only expose that implicitly.

So, in my (errs's) view as a types team member, there's nothing for types to weigh in on here with respect to the implementation being stabilized, and I'd suggest a lang-only proposed FCP (though we'll of course CC the team below).

Authorship and acknowledgments

This stabilization report was coauthored by compiler-errors and TC.

TC would like to acknowledge the outstanding and speedy work that compiler-errors has done to make this feature happen.

compiler-errors thanks TC for authoring the RFC, for all of his involvement in this feature's development, and pushing the Rust 2024 edition forward.

Open items

We're doing some things in parallel here. In signaling the intention to stabilize, we want to uncover any latent issues so we can be sure they get addressed. We want to give the maximum time for discussion here to happen by starting it while other remaining miscellaneous work proceeds. That work includes:

(Selected) implementation history

Closes #123432.

cc @rust-lang/lang @rust-lang/types

@rustbot labels +T-lang +I-lang-nominated +A-impl-trait +F-precise_capturing

Tracking:


For the compiler reviewer, I'll leave some inline comments about diagnostics fallout :^)

r? compiler

@compiler-errors compiler-errors added T-lang Relevant to the language team, which will review and decide on the PR/issue. A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. I-lang-nominated Nominated for discussion during a lang team meeting. F-precise_capturing `#![feature(precise_capturing)]` labels Jul 13, 2024
@rustbot rustbot added A-rustdoc-json Area: Rustdoc JSON backend S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 13, 2024
@compiler-errors compiler-errors assigned oli-obk and unassigned TaKO8Ki Jul 13, 2024
@compiler-errors compiler-errors removed the A-rustdoc-json Area: Rustdoc JSON backend label Jul 13, 2024
|
LL | async fn async_ret_impl_trait1<'a, 'b>(a: &'a u8, b: &'b u8) -> impl Trait<'a> + 'b {
| ++++
LL | async fn async_ret_impl_trait1<'a, 'b>(a: &'a u8, b: &'b u8) -> impl Trait<'a> + use<'a, 'b> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first of all, we now always suggest + use<'a> instead of + 'a to capture a lifetime, since it's strictly more accurate

|
LL | fn step2<'a, 'b: 'a>() -> impl Sized + 'a + 'b {
| ++++
LL | fn step2<'a, 'b: 'a>() -> impl Sized + 'a + use<'a, 'b> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could make this suggestion better by removing any + 'a, though I do think we need to be careful about preserving meaning...

|
LL | fn chars0<'a>(v :(&'a str, &'a str)) -> impl Iterator<Item = char> + 'a {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We however have lost the suggestion to give the lifetimes a name so they can be referenced in the opaque, so this + use<'_> is now wrong. I could recover it, but would like to do it asynchronously :)

@@ -9,11 +9,6 @@ LL | fn get_one<'a>(a: *mut &'a str) -> impl IntoIterator<Item = Opaque<'a>> {
...
LL | None::<Opaque<'static>>
| ^^^^^^^^^^^^^^^^^^^^^^^
|
help: to declare that `impl IntoIterator<Item = Opaque<'a>>` captures `'a`, you can add an explicit `'a` lifetime bound
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggestion was wrong -- we were suggesting to add the + 'a to the wrong opaque -- one that already captured 'a.

@tgross35
Copy link
Contributor

I understand that this needs to be stabilized before edition 2024, but is there any specific reason not to wait as long as possible to gather at least some experimental usage & feedback? It has only been a month since the RFC merged, I don't think many people can be aware this exists.

Also syn doesn't support it yet - not necessarily a blocker, but it does mean precise capturing + macros = confusing errors for anyone who does try it out.

Maybe at least a call for testing blog post would help increase awareness?

@traviscross
Copy link
Contributor

traviscross commented Jul 13, 2024

Thanks for pointing out syn. In posting the stabilization report and thereby signaling the intention to stabilize, we mean to uncover things like that so that we can work to get them resolved.

More widely, the unfortunate reality is that most nightly-only features like this just don't get a lot of actual testing in nightly.1 So there is often limited testing value in leaving an otherwise-ready feature in nightly for an extended period of time.2

In this case, what's being stabilized are the minimal and well-understood pieces. As the stabilization report describes, we're lowering this to expose existing type system features in which we have high confidence.3

There are three key reasons to get this out sooner rather than later:

  1. It gives people more time to migrate their code ahead of the edition.
  2. Independent of the edition, it solves a real problem that users have (overcapturing of lifetimes), and it's worth delivering that value to our users on stable Rust.
  3. It derisks the edition.

Putting on my edition hat, number 3 is really key here. To achieve our goals for the edition, which are:

  • Make this edition a success.
  • Do so without requiring heroics from anyone.
    • ...or stressing anyone or everyone out.

We need to be (and are) crossing off as many items as possible as soon as possible. Things always go wrong, and trying to wait until the last minute for anything is a recipe for putting the edition plans at risk or stressing everyone out to try to make a diving save.

This isn't the only item that's going to be like this. We have other edition items where the feature needs to be stabilized in all editions ahead of the edition. For those too, we're going to want stabilizations to happen sooner rather than later4 even though these features have landed only recently in nightly.

Our go / no go date for all items is less than three months out at this moment. That will go by in a blink. The edition is on track. But to keep it that way, there just isn't that much time that we can delay.5

Footnotes

  1. This is, in a way, a success. We want people to use stable Rust and not come to rely on nightly features.

  2. Looking at a calendar, this isn't going to stabilize any sooner than Rust 1.82 which will be released on 2024-10-17. So even if this stabilization entered FCP today, there's yet some further time for testing and for things like syn to potentially make accommodations. That will be 6 months after the feature landed in nightly.

  3. It's not without relevance that we have high confidence in the implementor here too.

  4. But no sooner than when it's ready and we have high confidence in it, of course.

  5. We need the things that are ready to get fully crossed off so we can focus on the things that are not yet.

@jackh726
Copy link
Member

jackh726 commented Jul 13, 2024

As a lang advisor, I want to second @tgross35's point and formally raise a concern. Can some lang member please register this with rfcbot when FCP starts (if this isn't resolved in some way).

To start, let me say that I quite like this feature and where it ended up in terms of how you specify the captured parameters. Overall, having this feature will make the language better.

That being said, I have very strong doubts that we as language developers have properly done our due diligence here to ensure that language users understand this feature, how to use it, and that it all-in-all brings down the net burden of learning Rust.

One reason for this concern is that impl Trait already is one of the more confusing parts of the language (in part because of the dichotomy between RPIT and APIT and in part because of it's just a being bit of tricky feature because of captures/lifetimes). I think it's also clear that we're still as language developers figuring out what opaque types mean and can do, given both ongoing work like TAITs, but also unanswered questions around higher-ranked lifetimes (which depending what we decide results in different outcomes for things like async closures). So, adding on additional complexity to impl Trait being "sure" that we understand how it affects the language for users for be a massive fail for us as language developers (not to mention that "additional complexity" is the largest thing users said they were worried about in the 2023 survery).

Another reason I'm raising this concern is that, especially given the above, in my option this feature has moved very fast through its developmental milestones and we ultimately lack data on how this might or will be used. The reasoning that "we don't expect much nightly usage" is extremely concerning. It is ultimately true, but that doesn't mean that we can't do better. To give some ideas, we could 1) do a thorough analysis of existing code to identify patterns were this needed or how it affects code readability 2) put out calls for testing or surveys of the proposed feature 3) dogfood this more, in the compiler itself for example.

To also add another point to the above: the discussion of this feature since conception has resulted in a significant change with the syntax change from use <..>impl Trait to impl Trait + use<..>. (Which as I have said before I do like, but is still a significant change.) This to me highlights the complexity in the feature and the need to properly understand its effect on language use.

All in all, I think it's pretty clear that the timeline for this is heavily accelerated by the edition. But let me note: there will be more editions. We do not need to, nor should we, rush features through because of some arbitrary constraint of them needing to make it into this edition. Ultimately, without the proper work I suggest above, I don't think this feature should make the edition and that's okay. And further, doing the things I suggest above is itself a big ask, and I don't think would get done in time for the edition (though, that's not a given).

@traviscross to directly address your "make the edition a success without requiring heroics or stressing anyone and everyone out": not landing this feature will not make this is edition unsuccessful, but rushing it definitely does stress people out (if not anybody else (which I doubt), at least very much me).

Our ethos is not to rush features for arbitrary deadlines. In the past, we've done a mixed job at following that with editions. This, in my opinion, is one of the stronger examples of us letting the timeline of the edition dictate when feature development milestones must happen by, rather than them co-occuring naturally and mostly independently.

And finally, I want to point out a large concern from me with the edition as it relates to this as well: we have already pushed the stability of the edition to 2025, in large part (likely, I haven't been a part of those decisions) because features like this could not have been ready in time. To me, this is a large hint that we're trying to do too much. I would much prefer a "smaller edition" (if you can say that, it's still 3 years of development work) than trying to fit everything in it for some arbitrary line of "success" at the expense of both a delay and rushing features through.

@BoxyUwU
Copy link
Member

BoxyUwU commented Jul 13, 2024

I'd definitely like to echo some of jack's points here.

Reading through TC's comment I generally get the impression that there "isn't much time" and we "have to stabilize everything ASAP", this really feels to me like the 2024 edition has "gone wrong" somehow. In an ideal world I don't think we should be 6months out from an edition release worrying about landing RFCs and stabilizing features.

In general I am a bit concerned that the "deadline" of the edition acts as an intentional (or unintentional) pressure to rush things in that are not necessarily ready or the best solution. I think regardless of the fact precise captures seems relatively straightforward and I'm in favour of it, the framing here of "we have to get this in quickly for the edition" makes me distinctly uncomfortable.

We have the train release schedule for a reason and the fact that things are happening so close to shipping the edition to the point where we're even talking about whether its possible to wait a few cycles before stabilization is deeply concerning to me.

I would definitely say that the feeling of there being so many "last minute" RFCs for this edition is stressing me out. As a concrete example the fact that Match Ergonomics 2.0 is seemingly still having its design figured out is so very worrying. Match ergonomics being probably one of the more controversial features the language has and we're increasing its strength all the while rushing to "get it out the door" for the edition.

I don't know that I really have a good solution here other than "it sure would be nice if we started work on the edition significantly earlier for 2027".


Interestingly, I don't think this really has anything to do with the "quality" of the feature- I find the broad strokes of the match ergonomics proposal to be quite compelling. I think never type stuff is super great. I also think precise captures is an incredibly good idea compared to simply shipping the edition captures changes without a way to pare down the captured lifetimes.

Its simply just that the passive pressure (or explicit pressuring in this PR) of the edition deadline makes the whole thing feel significantly scarier than it otherwise would be.

@traviscross
Copy link
Contributor

traviscross commented Jul 13, 2024

@jackh726: If you would, it would be helpful if you could describe concretely what you would want to see happen to resolve your concern. I.e., what's the acceptance criteria for your concern to be resolved? E.g., is there a specific amount of time you want to see this sit in nightly? Is there a specific amount of usage you want to see of this in the ecosystem? Is there a way you would suggest to measure when "we as language developers have properly done our due diligence here to ensure that users understand this feature, how to use it, and that it all-in-all brings down the net burden of learning Rust" (and how do you weigh that against the other reasons to do this)?

With respect to this feature and the edition, not landing this means not landing the Lifetime Capture Rules 2024, which means that the capture rules would work inconsistently between RPIT in bare functions and inherent impls and RPIT in trait impls and in RPITIT1 throughout the next edition, as the new rules are already stabilized for the latter (which was done on the expectation of stabilizing these rules across the board in Rust 2024). It would mean leaving unaddressed for the next edition all of the problems that led us to adopt RFC 3498.

It's for these reasons that lang identified this as priority item for Rust 2024. Not landing this or the other lang priority items, would, in my view, make the edition less of a success.

With respect to the edition schedule more broadly -- and I don't want to make this thread about that, so let's please take this aside if we have anything further -- we rescheduled the edition because the other schedule that we inherited wasn't realistic.2 None of the lang priority items would have shipped. Shipping almost anything at all in the edition would have required heroics and would have stressed everyone out. Cutting everything or almost everything would have wasted a ton of work that people had already done, probably burning many of those people out. It didn't seem worth doing. Taking from the rescheduling the implication that we're trying to do too much is not grounded in the facts of the situation, in my view.

Footnotes

  1. ...and in ATPIT and TAIT, when we stabilize those, which we expect to do early in Rust 2024.

  2. Mistakes were made.

@BoxyUwU
Copy link
Member

BoxyUwU commented Jul 13, 2024

On a completely unrelated note, the PR description is absolutely fantastic and did a great job of answering any questions I might have had about this. Huge gg to both errs and tc for that

@compiler-errors
Copy link
Member Author

compiler-errors commented Jul 13, 2024

author note read this 😺

Howdy y'all. First of all, thanks for the feedback. For the record, I expected this almost certainly to happen, I hope there's no hard feelings mutually on this. Pardon me, I could've probably also made this a zulip thread on T-lang going "are we ready yet?", but also there's always the risk that we do that, and people still come out of the woodwork on the stabilization PR, so 🤷 😸.

One problem here is that we don't really have a policy for exposing users to a new nightly feature, teaching them about it, etc. Sometimes this happens after the stabilization PR merges (e.g. on a blog post which happens after), but this is especially uncommon before stabilization, and if it does happen, then it currently happens via osmosis, or for the largest features incidentally (e.g. via a blog post that serves an orthogonal purpose).

Many features in Rust sit around unstabilized (but also not gaining much more user exposure at all 😿) for years because they're otherwise blocked on either implementation challenges, or someone hasn't sat down and thought about the feature's consistency as a whole (e.g. associated type bounds), or simply because nobody has written up a stabilization report. The difficulty of developing things in Rust is a natural speed-bump for getting things done, but also that cannot be the only thing that ensures we temper our enthusiasm for stabilizing things we think are ready for prime-time.

So when that doesn't happen (i.e. when developing a feature is straightforward, and it serves a clear purpose in the language, the design isn't particularly ambiguous except for perhaps syntax, etc), such as with this feature, it can feel rushed for two separate but somewhat easy to mix up reasons -- one, is that the stabilization is happening quickly, and the second is the feeling that we haven't done our due diligence regarding making sure the feature is ready for stabilization yet. If it were just the former, then that's actually a good thing IMO, because I think on the contrary the Rust community often perceives that we as compiler developers don't get things done soon enough for what seems like arbitrary process reasons (cue the features that people have been begging about for years -- this is actually one of them, at least at a high level).

BUT regarding that second one which is more important here -- the perception that we haven't done our due diligence making sure the feature is ironed out and ready -- I'll admit that we may be lacking exposure for this feature and haven't made folks as confident as we are regarding whether this is ready 😺 So yeah, as TC said above, it would be very cool to know what people want to see regarding that. Super actionable things like dogfooding in the compiler, making a blog post about it, etc. are all great ideas and I'm interested in doing that very soon.

I do think we really should strike a balance here though, because implementation-wise this feature is pretty simple (in the compiler) and a time-based criteria for waiting for things to be "ready" somewhat discredits that fact, so I'd like to see more objective, tangible criteria that can be used to gauge a feature's stabilization readiness. I really hope that whatever we learn from this stabilization PR can help us form a larger roadmap skeleton for stabilizing features in the future. Developing and especially stabilizing shit in Rust is incredibly ad-hoc, and it feels like we're inventing policy as we go all the time 😸

@traviscross
Copy link
Contributor

traviscross commented Jul 13, 2024

I'm going to say one last thing about the edition because I've received evidence some may have taken the wrong idea from one of the messages above about it.

The edition is on track.

On the edition team, we have no concerns about how things are going with respect to the edition. We're crossing off items at the rate we expect. All items have owners. Those owners are moving things along, and we have good communication with them and with the relevant teams. We are confident we're going to succeed in:

  • Making this edition a success.
  • Doing so without requiring heroics from anyone.
    • ...or stressing anyone or everyone out.

The point above is about what we need to see happen to keep it on track.

The plan that's been working has been to push all of the items along as we can to completion, and to cross off items that can be made ready as soon as possible so we can focus on the remaining items.

What would break this system is to start saying for the items, "oh, we have M months left, let's hold off for M - ε on this item." Then 1) we'd end up behind the airplane and 2) a bunch of items and remaining work and risk would pile up at the end. That's what we cannot do.

@tgross35
Copy link
Contributor

tgross35 commented Jul 13, 2024

I don't necessarily think that moving swiftly is a problem, and, like the others, I think this feature is a step in the right direction. However: I would suggest that if time for natural community engagement is cut short, it needs to be compensated with effort to somehow get that community engaged. This is especially true with things that arguably make the language more complex, given the general awareness of that area.

A related concern is that the feature appears reasonable, but I haven't found any real-world use to see how it feels in situ. It does not appear to be used within the rust-lang org, nor in any codebase on GitHub (currently 191 total hits, all look like false positives).

Actionable items that I think may improve the situation:

  1. A blog post giving the bigger plan of 2024 lifetime captures and its status would be great. Just saying what components are implemented and ready for testing, what is expected to stabilize next or how that will be broken up, what still has work to do, what lints are coming, how to test it, etc. (Totally different feature but the GATs post did a great job of this). I think everyone not on the design team was a bit caught off guard by this PR precisely because very few people were aware it was stable enough for experimentation, much less a stabilization candidate.

  2. Update some codebase somewhere to make use of this feature, since it is helpful to see how things change. Agreed with the above that updating the compiler makes sense, but that doesn't have to be it.

  3. This isn't the only item that's going to be like this. We have other edition items where the feature needs to be stabilized in all editions ahead of the edition. For those too, we're going to want stabilizations to happen sooner rather than later even though these features have landed only recently in nightly.

    If there are more features requiring accelerated timelines, they should probably follow similar guidelines. That is, as soon as they are usable enough, put out a call for testing (probably just a IRLO/URLO announcement works) as part of due diligence.

In general I do think some policy would help here; when rust-lang/rfcs#3668 was posted and FCP proposal started immediately, I considered suggesting that RFCs should get at least two months from their first TWIR post (which still seems entirely reasonable to me). Two months on nightly after implementation also seems reasonable - enough time for people to come back from vacation, learn about it, come across a use case for it, try it out, and provide feedback. I understand that this PR, and any related features coming, will already be an exception.

Again, I feel positive about going forward here - the writeup is great, the new rules make sense, and I like the new syntax a lot more. We just need to make sure the wider Rust community doesn't feel shorted of its chance to provide input because trying to meet a deadline meant that the feedback loop got cut short.

@traviscross
Copy link
Contributor

traviscross commented Jul 13, 2024

Thanks @tgross35. Those are all reasonable points, in my view. Some further context and thoughts follow.

Merging a PR to replace Captures with use<..> in rustc

Regarding updating the compiler to use this feature, we had actually done that ahead of writing the RFC, and in the RFC, we linked to that diff as an example of what this looked like "in action". We didn't merge it at the time only due to the unpleasantness of adding #![allow(incomplete_features)] everywhere. We'll be updating this to the revised syntax and expect to have a PR up soon. I've already seen a draft of this.

Education

One main way that people will find out about and be educated about this feature ahead of and after the release of Rust 2024 is in the edition guide. There will be extensive discussion about the changes to the lifetime capture rules, and that discussion will prominently feature the use<..> syntax that we're stabilizing here. One reason we'd prefer this feature stabilized sooner rather than later is so that we can add this documentation in a way that allows readers today to follow along and migrate their code on stable Rust.

Outreach and communication

Agreed that more outreach is good.

The RFC was itself a kind of outreach, as is this stabilization report, and this also appeared as part of a flagship Rust project goal, but we can always do more.

Process and policy

Proposals to change processes often come up in issues like this. There are things we could talk about doing, e.g. extending the FCP window, but it's probably better to discuss those separately.

@Mark-Simulacrum
Copy link
Member

is there a specific amount of time you want to see this sit in nightly?

Very concretely, it is quite unsurprising to me that no one has used this feature even if they typically use nightly and may have wanted to. This PR moves this feature gate from being tagged as an "incomplete feature" to stable in one go. I think if we expect nightly usage -- whether in rustc or beyond -- it's very reasonable to expect that users won't reach for features we're explicitly telling them not to use. (At least, that's my understanding of what incomplete feature means).

This is even why we didn't yet experience what at least compiler devs thought of seeing it in code, experiencing things like (just as a potential example) derives/macros breaking due to lack of syn support, rust-analyzer error'ing out, maybe rustfmt breaking... historically some of these have been treated as "not a blocker" by lang (though I'm not aware of policy/RFC decisions on the general question), but I think having them as inputs is useful.

We didn't merge it at the time only due to the unpleasantness of adding #![allow(incomplete_features)] everywhere. We'll be updating this to the revised syntax and expect to have a PR up soon. I've already seen a draft of this.

I think it's pretty reasonable that we allow for some time to go by in between a feature being "done" and a feature being proposed for stabilization. The more esoteric/niche features get (as we should expect over time) the harder it is to get people to use them on nightly, but at least we can try to dog-food them ourselves and as a fallback have a partner user of some kind that has in fact used these in some code base and can report on how that went.

@compiler-errors
Copy link
Member Author

compiler-errors commented Jul 14, 2024

@Mark-Simulacrum:

This PR moves this feature gate from being tagged as an "incomplete feature" to stable in one go.

One nit -- This feature is not marked incomplete, it's marked unstable. I don't want people to get an even worse feeling about this stabilization via that misimpression, lol.

@Mark-Simulacrum
Copy link
Member

Oh, never mind then :) I think I was mislead by the removal of incomplete feature lint-gates in a few places in the PR. Still, I would prefer to see some experience with the feature interacting with tooling etc. before we stabilize. It does look like rustfmt support has been implemented though which is great to see!

@compiler-errors
Copy link
Member Author

compiler-errors commented Jul 14, 2024

Yep, I'm happy to wait and show what this feature can do to clean up opaques in the compiler, at the very least.

@traviscross
Copy link
Contributor

traviscross commented Jul 14, 2024

I agree with the core of the point @Mark-Simulacrum makes here. In particular:

...experiencing things like (just as a potential example) derives/macros breaking due to lack of syn support, rust-analyzer error'ing out, maybe rustfmt breaking... historically some of these have been treated as "not a blocker" by lang... but I think having them as inputs is useful.

These are indeed useful inputs, and we should seek to discover and weigh (or, preferably, resolve) concrete considerations like this. I've added the known open items to the stabilization report.

We're doing some things in parallel here. We posted the stabilization report so as to prompt and start these conversations. In signaling the intention to stabilize, we wanted to uncover any latent issues so we can be sure they get addressed. We wanted to give the maximum time for these conversations to happen by starting them while other work (such as using the feature within the compiler and merging PRs to various non-rustc tooling) proceeds in parallel.

That is, putting up the stabilization report is the start of a process, not the end of one.

Copy link
Contributor

@traviscross traviscross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes here for this stabilization look right to me.

@AlseinX

This comment was marked as off-topic.

@compiler-errors

This comment was marked as resolved.

@spastorino
Copy link
Member

@bors r+ rollup=never

@bors
Copy link
Contributor

bors commented Aug 20, 2024

📌 Commit 84044cd has been approved by spastorino

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 20, 2024
@bors
Copy link
Contributor

bors commented Aug 20, 2024

⌛ Testing commit 84044cd with merge 4aaf75f...

bors added a commit to rust-lang-ci/rust that referenced this pull request Aug 20, 2024
…=spastorino

Stabilize opaque type precise capturing (RFC 3617)

This PR partially stabilizes opaque type *precise capturing*, which was specified in [RFC 3617](rust-lang/rfcs#3617), and whose syntax was amended by FCP in [rust-lang#125836](rust-lang#125836).

This feature, as stabilized here, gives us a way to explicitly specify the generic lifetime parameters that an RPIT-like opaque type captures.  This solves the problem of overcapturing, for lifetime parameters in these opaque types, and will allow the Lifetime Capture Rules 2024 ([RFC 3498](rust-lang/rfcs#3498)) to be fully stabilized for RPIT in Rust 2024.

### What are we stabilizing?

This PR stabilizes the use of a `use<'a, T>` bound in return-position impl Trait opaque types.  Such a bound fully specifies the set of generic parameters captured by the RPIT opaque type, entirely overriding the implicit default behavior.  E.g.:

```rust
fn does_not_capture<'a, 'b>() -> impl Sized + use<'a> {}
//                               ~~~~~~~~~~~~~~~~~~~~
//                This RPIT opaque type does not capture `'b`.
```

The way we would suggest thinking of `impl Trait` types *without* an explicit `use<..>` bound is that the `use<..>` bound has been *elided*, and that the bound is filled in automatically by the compiler according to the edition-specific capture rules.

All non-`'static` lifetime parameters, named (i.e. non-APIT) type parameters, and const parameters in scope are valid to name, including an elided lifetime if such a lifetime would also be valid in an outlives bound, e.g.:

```rust
fn elided(x: &u8) -> impl Sized + use<'_> { x }
```

Lifetimes must be listed before type and const parameters, but otherwise the ordering is not relevant to the `use<..>` bound.  Captured parameters may not be duplicated.  For now, only one `use<..>` bound may appear in a bounds list.  It may appear anywhere within the bounds list.

### How does this differ from the RFC?

This stabilization differs from the RFC in one respect: the RFC originally specified `use<'a, T>` as syntactically part of the RPIT type itself, e.g.:

```rust
fn capture<'a>() -> impl use<'a> Sized {}
```

However, settling on the final syntax was left as an open question.  T-lang later decided via FCP in [rust-lang#125836](rust-lang#125836) to treat `use<..>` as a syntactic bound instead, e.g.:

```rust
fn capture<'a>() -> impl Sized + use<'a> {}
```

### What aren't we stabilizing?

The key goal of this PR is to stabilize the parts of *precise capturing* that are needed to enable the migration to Rust 2024.

There are some capabilities of *precise capturing* that the RFC specifies but that we're not stabilizing here, as these require further work on the type system.  We hope to lift these limitations later.

The limitations that are part of this PR were specified in the [RFC's stabilization strategy](https://rust-lang.github.io/rfcs/3617-precise-capturing.html#stabilization-strategy).

#### Not capturing type or const parameters

The RFC addresses the overcapturing of type and const parameters; that is, it allows for them to not be captured in opaque types.  We're not stabilizing that in this PR.  Since all in scope generic type and const parameters are implicitly captured in all editions, this is not needed for the migration to Rust 2024.

For now, when using `use<..>`, all in scope type and const parameters must be nameable (i.e., APIT cannot be used) and included as arguments.  For example, this is an error because `T` is in scope and not included as an argument:

```rust
fn test<T>() -> impl Sized + use<> {}
//~^ ERROR `impl Trait` must mention all type parameters in scope in `use<...>`
```

This is due to certain current limitations in the type system related to how generic parameters are represented as captured (i.e. bivariance) and how inference operates.

We hope to relax this in the future, and this stabilization is forward compatible with doing so.

#### Precise capturing for return-position impl Trait **in trait** (RPITIT)

The RFC specifies precise capturing for RPITIT.  We're not stabilizing that in this PR.  Since RPITIT already adheres to the Lifetime Capture Rules 2024, this isn't needed for the migration to Rust 2024.

The effect of this is that the anonymous associated types created by RPITITs must continue to capture all of the lifetime parameters in scope, e.g.:

```rust
trait Foo<'a> {
    fn test() -> impl Sized + use<Self>;
    //~^ ERROR `use<...>` precise capturing syntax is currently not allowed in return-position `impl Trait` in traits
}
```

To allow this involves a meaningful amount of type system work related to adding variance to GATs or reworking how generics are represented in RPITITs.  We plan to do this work separately from the stabilization.  See:

- rust-lang#124029

Supporting precise capturing for RPITIT will also require us to implement a new algorithm for detecting refining capture behavior.  This may involve looking through type parameters to detect cases where the impl Trait type in an implementation captures fewer lifetimes than the corresponding RPITIT in the trait definition, e.g.:

```rust
trait Foo {
    fn rpit() -> impl Sized + use<Self>;
}

impl<'a> Foo for &'a () {
    // This is "refining" due to not capturing `'a` which
    // is implied by the trait's `use<Self>`.
    fn rpit() -> impl Sized + use<>;

    // This is not "refining".
    fn rpit() -> impl Sized + use<'a>;
}
```

This stabilization is forward compatible with adding support for this later.

### The technical details

This bound is purely syntactical and does not lower to a [`Clause`](https://doc.rust-lang.org/1.79.0/nightly-rustc/rustc_middle/ty/type.ClauseKind.html) in the type system.  For the purposes of the type system (and for the types team's curiosity regarding this stabilization), we have no current need to represent this as a `ClauseKind`.

Since opaques already capture a variable set of lifetimes depending on edition and their syntactical position (e.g. RPIT vs RPITIT), a `use<..>` bound is just a way to explicitly rather than implicitly specify that set of lifetimes, and this only affects opaque type lowering from AST to HIR.

### FCP plan

While there's much discussion of the type system here, the feature in this PR is implemented internally as a transformation that happens before lowering to the type system layer.  We already support impl Trait types partially capturing the in scope lifetimes; we just currently only expose that implicitly.

So, in my (errs's) view as a types team member, there's nothing for types to weigh in on here with respect to the implementation being stabilized, and I'd suggest a lang-only proposed FCP (though we'll of course CC the team below).

### Authorship and acknowledgments

This stabilization report was coauthored by compiler-errors and TC.

TC would like to acknowledge the outstanding and speedy work that compiler-errors has done to make this feature happen.

compiler-errors thanks TC for authoring the RFC, for all of his involvement in this feature's development, and pushing the Rust 2024 edition forward.

### Open items

We're doing some things in parallel here.  In signaling the intention to stabilize, we want to uncover any latent issues so we can be sure they get addressed.  We want to give the maximum time for discussion here to happen by starting it while other remaining miscellaneous work proceeds.  That work includes:

- [x] Look into `syn` support.
  - dtolnay/syn#1677
  - dtolnay/syn#1707
- [x] Look into `rustfmt` support.
  - rust-lang#126754
- [x] Look into `rust-analyzer` support.
  - rust-lang/rust-analyzer#17598
  - rust-lang/rust-analyzer#17676
- [x] Look into `rustdoc` support.
  - rust-lang#127228
  - rust-lang#127632
  - rust-lang#127658
- [x] Suggest this feature to RfL (a known nightly user).
- [x] Add a chapter to the edition guide.
  - rust-lang/edition-guide#316
- [x] Update the Reference.
  - rust-lang/reference#1577

### (Selected) implementation history

* rust-lang/rfcs#3498
* rust-lang/rfcs#3617
* rust-lang#123468
* rust-lang#125836
* rust-lang#126049
* rust-lang#126753

Closes rust-lang#123432.

cc `@rust-lang/lang` `@rust-lang/types`

`@rustbot` labels +T-lang +I-lang-nominated +A-impl-trait +F-precise_capturing

Tracking:

- rust-lang#123432

----

For the compiler reviewer, I'll leave some inline comments about diagnostics fallout :^)

r? compiler
@rust-log-analyzer
Copy link
Collaborator

The job x86_64-msvc-ext failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
[RUSTC-TIMING] miri test:false 4.524
error: failed to remove file `C:\a\rust\rust\build\x86_64-pc-windows-msvc\stage1-tools\x86_64-pc-windows-msvc\release\miri.exe`

Caused by:
  Access is denied. (os error 5)
Command has failed. Rerun with -v to see more details.
  local time: Tue, Aug 20, 2024  5:46:19 AM
  network time: Tue, 20 Aug 2024 05:46:20 GMT
##[error]Process completed with exit code 1.
Post job cleanup.

@bors
Copy link
Contributor

bors commented Aug 20, 2024

💔 Test failed - checks-actions

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Aug 20, 2024
@traviscross
Copy link
Contributor

@bors retry "failed to remove file miri.exe" on Windows, CC #127883

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 20, 2024
@bors
Copy link
Contributor

bors commented Aug 20, 2024

⌛ Testing commit 84044cd with merge a971212...

@bors
Copy link
Contributor

bors commented Aug 20, 2024

☀️ Test successful - checks-actions
Approved by: spastorino
Pushing a971212 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Aug 20, 2024
@bors bors merged commit a971212 into rust-lang:master Aug 20, 2024
7 checks passed
@rustbot rustbot added this to the 1.82.0 milestone Aug 20, 2024
@ehuss ehuss added the relnotes Marks issues that should be documented in the release notes of the next release. label Aug 20, 2024
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (a971212): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results (primary -7.4%, secondary -3.7%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
-7.4% [-7.4%, -7.4%] 1
Improvements ✅
(secondary)
-3.7% [-3.7%, -3.7%] 1
All ❌✅ (primary) -7.4% [-7.4%, -7.4%] 1

Cycles

Results (secondary 2.4%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
2.4% [2.1%, 2.7%] 7
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 749.811s -> 749.153s (-0.09%)
Artifact size: 338.86 MiB -> 338.89 MiB (0.01%)

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Aug 22, 2024
@Mark-Simulacrum Mark-Simulacrum added relnotes Marks issues that should be documented in the release notes of the next release. and removed relnotes Marks issues that should be documented in the release notes of the next release. labels Aug 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-impl-trait Area: impl Trait. Universally / existentially quantified anonymous types with static dispatch. A-rustdoc-json Area: Rustdoc JSON backend disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-precise_capturing `#![feature(precise_capturing)]` finished-final-comment-period The final comment period is finished for this PR / Issue. merged-by-bors This PR was explicitly merged by bors. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for precise_capturing