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

Precise capturing #3617

Merged
merged 17 commits into from
Jun 5, 2024
Merged

Precise capturing #3617

merged 17 commits into from
Jun 5, 2024

Conversation

traviscross
Copy link
Contributor

@traviscross traviscross commented Apr 24, 2024

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-like impl 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 Rust use 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:

In the end, we decided that use<..> would appear as a syntactic bound.

Tracking:

@traviscross traviscross added the T-lang Relevant to the language team, which will review and decide on the RFC. label Apr 24, 2024
@traviscross traviscross force-pushed the TC/precise-capturing branch 2 times, most recently from f60865c to 0231781 Compare April 24, 2024 08:01
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.
// ^ 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.
Copy link

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.

Copy link
Contributor

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".

text/3617-precise-capturing.md Outdated Show resolved Hide resolved
text/3617-precise-capturing.md Outdated Show resolved Hide resolved
@compiler-errors compiler-errors marked this pull request as ready for review April 24, 2024 11:57

## 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.:
Copy link
Contributor

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.)

Copy link
Member

@compiler-errors compiler-errors Apr 24, 2024

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.

Copy link
Contributor

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".

Copy link
Member

@compiler-errors compiler-errors Apr 24, 2024

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.)
@traviscross traviscross added the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label Apr 24, 2024
@nikomatsakis
Copy link
Contributor

@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 use, I think it's the best option overall.

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.

@rfcbot
Copy link
Collaborator

rfcbot commented Apr 24, 2024

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.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. labels Apr 24, 2024

We considered a number of different possible syntaxes before landing on `impl use<..> Trait`. We'll discuss each considered.

### `impl use<..> Trait`
Copy link
Contributor

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.
Copy link
Contributor

@Jules-Bertholet Jules-Bertholet Apr 24, 2024

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.:
Copy link
Contributor

@Jules-Bertholet Jules-Bertholet Apr 24, 2024

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.

@rfcbot rfcbot added final-comment-period Will be merged/postponed/closed in ~10 calendar days unless new substational objections are raised. and removed proposed-final-comment-period Currently awaiting signoff of all team members in order to enter the final comment period. labels Apr 24, 2024
@rfcbot
Copy link
Collaborator

rfcbot commented Apr 24, 2024

🔔 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.
@traviscross traviscross removed the I-lang-nominated Indicates that an issue has been nominated for prioritizing at the next lang team meeting. label Apr 24, 2024
@kennytm
Copy link
Member

kennytm commented Apr 25, 2024

Since this use<..> thing is part of the type signature, could you clarify if the capture list is changed, when it will be considered breaking change or not?

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.)

@jackh726
Copy link
Member

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.)
@traviscross
Copy link
Contributor Author

traviscross commented May 5, 2024

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 Self) in the trait definition. This is not likely to be possible in initial rounds of stabilization, and if it ever does become possible, these kind of questions are likely to receive extensive coverage in the stabilization report.

@Evian-Zhang
Copy link

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 use<> in the return type position to indicate it does not capture anything?

I think this pattern is very common and basic in most crates. Should there be some special treatment in rustdoc for use<> (i.e. capture nothing) to make it more beginner-friendly?

@nikomatsakis
Copy link
Contributor

@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 impl Trait for those kind of coercions. The right place for them to take place is in the caller side, the callee would prefer to just get a value of known type and hence be (more) monomorphic. We had at some point discussed a syntax option for this (e.g., writing ~String instead of impl Into<String>). That said, I hadn't until now considered the implications for borrow checking when you have an -> impl Trait return value.

(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.)

@Evian-Zhang
Copy link

@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 use<> problem in the tracking issues after this RFC stabilized? Apologies for putting it here.

@traviscross
Copy link
Contributor Author

traviscross commented May 6, 2024

@Evian-Zhang: It'd be better to open a thread on Zulip (or perhaps on IRLO) to discuss that further.

@nikomatsakis
Copy link
Contributor

@Evian-Zhang (...and no apologies required)

@nikomatsakis
Copy link
Contributor

@traviscross I'd like to add an unresolved question about the use<> impl Trait vs impl use<> Trait. My experience was that I thought I didn't care but within a few minutes of using it I found that use<> impl just felt much simpler to me. The best explanation I can give is that it feels less "stacked" -- i.e., I can look at the use and process it as a kind of "prefix" on the impl Trait type, rather than being an inner part that I have to think carefully about. That combined with the different scoping relative to for feels like a very strong argument to me.

@traviscross
Copy link
Contributor Author

traviscross commented May 31, 2024

Done. This unresolved question has now been added.

We leave as an open question which of these two syntaxes we should choose:

  1. impl use<..> Trait
    • This syntax is used throughout this RFC.
  2. use<..> impl Trait
    • This syntax is the worthy challenger.

See the alternatives section above for a detailed comparative analysis of these options.

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.
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.
@SOF3

This comment was marked as outdated.

@traviscross traviscross merged commit b57be11 into master Jun 5, 2024
@traviscross

This comment was marked as outdated.

@traviscross
Copy link
Contributor Author

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:

Comment on lines +149 to +155
We can capture elided lifetimes:

```rust
fn foo(x: &()) -> impl use<'_> Sized { x }
// ^^^^^^^^^^^^^^^^^^
// ^ Captures `'_` only.
```
Copy link

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.
  • 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.

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
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
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Aug 26, 2024
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This RFC is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this RFC. T-lang Relevant to the language team, which will review and decide on the RFC. to-announce
Projects
None yet
Development

Successfully merging this pull request may close these issues.