-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Precise capturing #3617
Precise capturing #3617
Conversation
f60865c
to
0231781
Compare
0231781
to
2a6cab8
Compare
To fully stabilize, in Rust 2024, the Lifetime Capture Rules 2024 that we accepted in RFC 3498, we need to stabilize some means of precise capturing. This RFC provides that means. We discussed this feature, the need for it, and the syntax for it in the T-lang planning meeting on 2024-04-03. The document here follows from that discussion.
2a6cab8
to
5ac2eb1
Compare
// ^ Captures `B`, `C`, and `D` but not `'a` or `A`. | ||
``` | ||
|
||
Here, the `..` means to include all in-scope generic parameters and `!` means to exclude a particular generic parameter even if previously included. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be nice to allow use<..>
from the start as an explicit way to capture all in-scope generic parameters. Using this short-hand together with explicit explicit captures or excluded captures should still remain future work.
Especially in code where precise captures would be frequently used, it might be preferable to always be explicit, even in cases where the implicit capture-all is what we want. Similar to how the syntax is used with struct initialisation, using use<..>
could mean that we know that we will always want to capture all generics, even if more are added, whereas manually enumerating each one might mean that we want to explicitly have to choose with every newly added generic.
Furthermore, since the stabilisation strategy suggests that at first only capturing all parameters may be supported, supporting use<..>
from the start would make this shorter to type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative meaning for use<..>
is "capture all APITs".
|
||
## Argument position impl Trait | ||
|
||
Note that for a generic type parameter to be captured with `use<..>` it must have a name. Anonymous generic type parameters introduced with argument position `impl Trait` (APIT) syntax don't have names, and so cannot be captured with `use<..>`. E.g.: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cannot be captured with
use<..>
Are they captured implicitly now?
If they are, then what is the opt out of that?
Are unnamed lifetimes implicitly captured by RPIT?
What is the opt out in that case?
(If unnamed early bound lifetimes are possible at all.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we don't capture APITs or elided lifetimes with use<'a, T>
. The opt-in here is to promote your APITs to real type generics and give your unnamed lifetimes names.
The only caveat here is that you can name the elided lifetime in the output in the same cases you are allowed to normally in RPITs -- i.e. fn hello(x: &u8) -> impl use<'_> Sized { x }
works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, we don't capture APITs or elided lifetimes with
use<'a, T>
.
My first question was whether they are captured if use
is not written.
The text says that all generic parameters are captured (on 2024 edition at least), but it's not clear whether APITs and elided lifetimes are included into this "all".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All type and const generic parameters (including APITs) are captured for all RPITs regardless if they're in edition 2021 or 2024.
The only change between 2021 and 2024 involves capturing all lifetimes parameters in scope (incl. elided lifetimes, since those become early- or late-bound lifetime params) -- previously we only captured lifetimes mentioned in the bounds of RPITs. The opt-out here is to use use<'a, T>
with the set of lifetimes that previously showed up in your opaque's bounds.
The only corner case is when you have an APIT and a lifetime you want to not capture -- you'll need to turn your APITs into real type parameters to make use of the use<'a, T>
syntax.
Regarding opt-out for capturing type or const generic parameters currently, it's not possible to do currently; we will likely support that eventually, but if you see https://github.com/rust-lang/rfcs/blob/TC/precise-capturing/text/3617-precise-capturing.md#stabilization-strategy, we're probably not going to stabilize that initially because it requires exercising new paths of the type system (namely, bivariant unconstrained type parameters), and it's not necessary to mitigate the fallout of the new edition 2024 lifetime capture rules.
One way to think about `use<..>` is that, in Rust `use` brings things into scope, and here we are bringing certain generic parameters into scope for the hidden type. Let's point this out.
We had included one `use<T>` in a pre-migration example when it should have only appeared in a post-migration example. Let's fix this error. (Thanks to @kennytm for pointing this out.)
@rfcbot fcp merge We've tried hard to avoid an explicit syntax like this but I think it's clear by now that it will be useful and it unblocks important Edition work. We had a reasonably thorough deep dive into the syntactic options and I believe the RFC lays out the options pretty well and the tradeoffs around them. Personally while I have some minor qualms about overloading Note that in this fcp I am explicitly wearing my @rust-lang/lang hat -- I think the @rust-lang/types team should (before stabilization) vet the overall semantics and our implementation thereof but that's not really a question to be answered in the RFC. |
Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns. |
|
||
We considered a number of different possible syntaxes before landing on `impl use<..> Trait`. We'll discuss each considered. | ||
|
||
### `impl use<..> Trait` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect that it's going to get annoying pretty quick (compared to impl<..> Trait
), if precise captures become something that is specified often (e.g. by convention to minimize captures).
|
||
Picking an existing keyword allows for this syntax, including extensions to other positions, to be allowed in older editions. Because `use` is a full keyword, we're not limited in where it can be placed. | ||
|
||
By not putting the generic parameters on `impl<..>`, we reduce the risk of confusion that we are somehow introducing generic parameters here rather than using them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel strongly about this syntax either way, but:
This would hardly be the only place where syntax for introducing generic parameters is similar to syntax for using them. fn foo<T>()
vs foo::<T>()
for example.
Also, there is a symmetry of a sort between the two kinds of impl
generics. In a trait implementation, they introduce generic parameters available for use by the type and trait; in RPIT, they would introduce the generics available for use by the hidden type.
} | ||
``` | ||
|
||
Here, the opaque type of the closure is capturing `T`. We may want a way to specify which outer generic parameters are captured by closure-like blocks. We could apply the `use<..>` syntax to closure-like blocks to solve this, e.g.: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't this "just work"? Shouldn't the compiler be able to figure out that the closure is not using T
? There should be no semver hazard, because to return the closure you need RPIT or TAIT , at which point you as API designer have the opportunity to specify the captures you commit to in public API.
🔔 This is now entering its final comment period, as per the review above. 🔔 |
For the formal syntax, we had used the existing `GenericParams` production. However, that production isn't exactly appropriate for this case. What's needed here is essentially a generic argument list that only accepts generic parameters as elements. Since none of the other existing productions provide this, we'll define our own. (Thanks to @kennytm for pointing this out.)
In the T-lang design meeting on 2024-04-24, a new syntax option was raised: `use<..> impl Trait`. While some people liked this, others did not, and no clear consensus formed to change the main proposal in this RFC. Nevertheless, let's discuss this as an alternative.
We had meant to say "parentheses" but had said "parenthesis" in two places. Let's fix that.
Since this From the caller's point of view, if a function's RPIT (in covariant position only) has a capture removed, the hidden type's potential shortest lifetime is lengthened, which is compatible with existing caller code. So I think semver should allow removing captures from covariant-position RPIT in a minor update. - fn callee1<'a, 'b>(aaa: &'a u8, bbb: &'b u8) -> &'a u8 { &0 }
+ fn callee1<'a, 'b>(aaa: &'a u8, bbb: &'b u8) -> &'static u8 { &0 }
- fn callee2<'a, 'b>(aaa: &'a u8, bbb: &'b u8) -> impl use<'a> Sized { &0 }
+ fn callee2<'a, 'b>(aaa: &'a u8, bbb: &'b u8) -> impl use<> Sized { &0 } Meanwhile for TAIT, ATPIT and RPITIT changing the capture list in either direction should be considered a major breaking change. (For APIT the capture list is irrelevant.) |
So, just procedurally... While I do think solving the underlying ergonomic and semantic issues here are quite important, I do have to note that this syntax and RFC seem to have moved quite fast. Even for me - who, while doesn't attend lang meetings, does try to keep up to date of active lang things - I could have almost missed this. To put some dates to this on typical milestones in our current processes:
I know we're on a time crunch because of the edition, but I worry about this all moving just a bit too fast. Of course, accepting this RFC doesn't necessarily mean that actually stabilizing this feature will also happen quickly, but I worry that the pace so far is a harbinger for that. I hate to sit here and be the one that say "wait, we're moving too fast", especially because not moving fast here puts edition work in jeopardy, but I'd rather this concern be voiced than ignored. I do want to be clear that my above comments have little reflection on my thoughts on the contents of this RFC or semantics of this feature. My concerns here apply to even the best of features (to put it into perspective: let-else, which imo is a very clear win all around took about a month from RFC open to RFC merge, with a fair amount of prior discussion; and this is what I consider a "fast" RFC process). |
During the FCP, some questions came up related to how refinement and reparameterization in the impl are handled. This handling is implied by other text in the RFC and by the existing behavior of Rust, so let's go ahead and add clarifications to address these questions. The hardest of these questions relate to how things would behave if we were to allow `use<..>` in trait definitions to not capture the generic input parameters to the trait (including `Self`). It's unlikely this will be possible for the foreseeable future, and while we will not leave these as open questions, certainly much might be learned between now and the point at which that might become possible, so we'll make note of that. We'll also add a clarification to address a question that came up in the 2024-04-24 design meeting about what it means to capture a const generic parameter. (Thanks to aliemjay for raising many of these great questions.)
Thanks @aliemjay for those great questions. We've now added examples and discussion to clarify each of those. Note that the hardest subset of those questions relate to how things would work if we were able to capture less than all of the generic input parameters to the trait (including |
Great work! I think this feature is a rather advanced feature and maybe this syntax appearing on simple APIs may discourage beginners when using fundamental functionalities in a crate. However, I have designed the following API pattern multiple times: fn parse(path: impl Path) -> impl Iterator<Item = Foo> {} I wonder if I should add a I think this pattern is very common and basic in most crates. Should there be some special treatment in rustdoc for |
@Evian-Zhang That's a good question (though not I think one that needs to block progress on the RFC). That said, I think even better would be if we could avoid using (We've also discussed (and even done some exploration of) having the compiler recognize the pattern of a "mostly monomorphic" function that just does transforms in the beginning and avoid code duplication: that's worth doing as a first step, I believe, but it wouldn't have the borrow checker benefits.) |
@nikomatsakis Thank you for your response! I agree that the coercions should be in the caller side, since I have found many places where the following code pattern appears in the Rust std source code: fn foo(path: impl AsRef<Path>) {
fn inner_foo(path: &Path) { ... }
inner_foo(path.as_ref())
} It is more graceful if this pattern can be automatically done by the compiler. I am not familiar with the RFC discussion guidelines, and I think maybe I should put the rustdoc's |
@Evian-Zhang: It'd be better to open a thread on Zulip (or perhaps on IRLO) to discuss that further. |
@Evian-Zhang (...and no apologies required) |
@traviscross I'd like to add an unresolved question about the |
Done. This unresolved question has now been added.
|
We had in this RFC chosen `impl use<..> Trait`. But there's been meaningful discussion about whether this should instead be `use<..> impl Trait`, so let's mark this question as unresolved.
20ded5f
to
3ede8f1
Compare
We had a sentence that would be correct if we had not already stabilized RPIT in trait impls. Since we have, let's more precisely describe where lifetime parameters are not implicitly captured.
The `use<..>` syntax is not part of any bound and must appear before all bounds. Let's say this a bit more clearly in one place.
Since we're leaving as open the question of whether to say `impl use<..> Trait` or `use<..> impl Trait`, we had earlier weakened some language that was previously decisive. We missed one spot, though, so let's make this sentence less decisive as well.
In a final read through the document, we found a few miscellaneous words and commas worth tweaking here and there, so let's tweak them.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
The lang team has accepted this RFC and we've now merged it. Thanks to all those who reviewed this and offered helpful feedback. For further updates, follow the tracking issue: |
We can capture elided lifetimes: | ||
|
||
```rust | ||
fn foo(x: &()) -> impl use<'_> Sized { x } | ||
// ^^^^^^^^^^^^^^^^^^ | ||
// ^ Captures `'_` only. | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not clearly documented about what "captures '_
only" means. It reads like it captures all elided lifetimes, but that's not the case.
After some testing on playground, it turns out '_
works as the same way as the '_
in a normal result type, that is:
- Refer to the only elided lifetime, if there is only a single elided lifetime and no named lifetimes.
- So you cannot use
'_
where there are named lifetimes, even if only a single lifetime is elided.
- So you cannot use
- Refer to the elided lifetime of
&self
if there is one. Other elided lifetimes are not captured. - Otherwise, report an ambiguity error.
Ideally we should document this somewhere.
…=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
…=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
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 [#125836](rust-lang/rust#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 [#125836](rust-lang/rust#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/rust#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/rust#126754 - [x] Look into `rust-analyzer` support. - rust-lang/rust-analyzer#17598 - rust-lang/rust-analyzer#17676 - [x] Look into `rustdoc` support. - rust-lang/rust#127228 - rust-lang/rust#127632 - rust-lang/rust#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/rust#123468 * rust-lang/rust#125836 * rust-lang/rust#126049 * rust-lang/rust#126753 Closes #123432. cc `@rust-lang/lang` `@rust-lang/types` `@rustbot` labels +T-lang +I-lang-nominated +A-impl-trait +F-precise_capturing Tracking: - rust-lang/rust#123432 ---- For the compiler reviewer, I'll leave some inline comments about diagnostics fallout :^) r? compiler
To fully stabilize, in Rust 2024, the Lifetime Capture Rules 2024 that we accepted in RFC 3498, we need to stabilize some means of precise capturing. This RFC provides that means.
This RFC adds
use<..>
syntax for specifying which generic parameters should be captured in an opaque RPIT-likeimpl Trait
type, e.g.impl Trait + use<'t, T>
(as amended in rust-lang/rust#125836). This solves the problem of overcapturing and will allow the Lifetime Capture Rules 2024 to be fully stabilized for RPIT in Rust 2024.One way to think about
use<..>
is that, in Rustuse
brings things into scope, and here we are bringing certain generic parameters into scope for the hidden type.For some history about the progress toward this feature predating this RFC, see this comment.
Rendered
Note that the final syntax was left as an open question in this RFC, and was later decided in:
impl Trait + use<..>
rust#125836In the end, we decided that
use<..>
would appear as a syntactic bound.Tracking:
precise_capturing
syntax rust#123432