From 5ac2eb1b408b21045e53c2816533f6dd76c26b95 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 22 Apr 2024 21:19:11 +0000 Subject: [PATCH 01/17] Add RFC 3617 for precise capturing 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. --- text/3617-precise-capturing.md | 668 +++++++++++++++++++++++++++++++++ 1 file changed, 668 insertions(+) create mode 100644 text/3617-precise-capturing.md diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md new file mode 100644 index 00000000000..4d9049a66ae --- /dev/null +++ b/text/3617-precise-capturing.md @@ -0,0 +1,668 @@ +- Feature Name: `precise_capturing` +- Start Date: 2024-04-03 +- RFC PR: [rust-lang/rfcs#3617](https://github.com/rust-lang/rfcs/pull/3617) +- Tracking Issue: [rust-lang/rust#123432](https://github.com/rust-lang/rust/issues/123432) + +# Summary +[summary]: #summary + +This RFC adds `use<..>` syntax for specifying which generic parameters should be captured in an opaque RPIT-like `impl Trait` type, e.g. `impl use<'t, T> Trait`. This solves the problem of overcapturing and will allow the Lifetime Capture Rules 2024 to be fully stabilized for RPIT in Rust 2024. + +# Motivation +[motivation]: #motivation + +## Background + +RPIT-like opaque `impl Trait` types in Rust *capture* certain generic parameters. + +*Capturing* a generic parameter means that parameter can be used in the hidden type later registered for that opaque type. Any generic parameters not captured cannot be used. + +However, captured generic parameters that are *not* used by the hidden type still affect borrow checking. This leads to the phenomenon of *overcapturing*. Consider: + +```rust +fn foo(_: T) -> impl Sized {} +// ^^^^^^^^^^ +// ^ The returned opaque type captures `T` +// but the hidden type does not. + +fn bar(x: ()) -> impl Sized + 'static { + foo(&x) +//~^ ERROR returns a value referencing data owned by the +//~| current function +} +``` + +In this example, we would say that `foo` *overcaptures* the type parameter `T`. The hidden type returned by `foo` does not *use* `T`, however it (and any lifetime components it contains) are part of the returned opaque type. This leads to the error we see above. + +Overcapturing limits how callers can use returned opaque types in ways that are often surprising and frustrating. There's no good way to work around this in Rust today. + +## Lifetime Capture Rules 2024 + +In Rust 2021 and earlier editions, all type parameters in scope are implicitly captured in RPIT-like `impl Trait` opaque types. In these editions, lifetime parameters are not implicitly captured unless named in the bounds of the opaque. This resulted, among other things, in the use of "the `Captures` trick". See [RFC 3498][] for more details about this. + +In RFC 3498, we decided to capture all in-scope generic parameters in RPIT-like `impl Trait` opaque types, across all editions, for new features we were stabilizing such as return position `impl Trait` in Trait (RPITIT) and associated type position `impl Trait` (ATPIT), and to capture all in-scope generic parameters for RPIT on bare functions and on inherent functions and methods starting in the Rust 2024 edition. Doing this made the language more predictable and consistent, eliminated weird "tricks", and, by solving key problems, allowed for the stabilization of RPITIT. + +However, the expansion of the RPIT rules in Rust 2024 means that some existing uses of RPIT, when migrated to Rust 2024, will now capture lifetime parameters that were not previously captured, and this may result in code failing to compile. For example, consider: + +```rust +//@ edition: 2021 +fn foo<'t>(_: &'t ()) -> impl Sized {} + +fn bar(x: ()) -> impl Sized + 'static { + foo(&x) +} +``` + +Under the Rust 2021 rules, this code is accepted because `'t` is not implicitly captured in the returned opaque type. When migrated to Rust 2024, the `'t` lifetime will be captured, and so this will fail to compile just as with the similar earlier example that had overcaptured a type parameter. + +We need some way to migrate this kind of code. + +[RFC 3498]: https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +In all editions, RPIT-like `impl Trait` opaque types may include `use<..>` in the bound to specify which in-scope generic parameters are captured or that no in-scope generic parameters are captured (with `use<>`). If `use<..>` is provided, it entirely overrides the implicit rules for which generic parameters are captured. + +For example, we can solve the overcapturing in the original motivating example by writing: + +```rust +fn foo(_: T) -> impl use<> Sized {} +// ^^^^^^^^^^^^^^^^ +// ^ Captures nothing. +``` + +Similarly, we can use this to avoid overcapturing a lifetime parameter so as to migrate code to Rust 2024:; + +```rust +fn foo<'t>(_: &'t ()) -> impl use<> Sized {} +// ^^^^^^^^^^^^^^^^ +// ^ Captures nothing. +``` + +We can use this to capture some generic parameters but not others: + +```rust +fn foo<'t, T, U>(_: &'t (), _: T, y: U) -> impl use Sized { y } +// ^^^^^^^^^^^^^^^^^ +// ^ Captures `U` only. +``` + +## Generic const parameters + +In addition to type and lifetime parameters, we can use this to capture generic const parameters: + +```rust +fn foo<'t, const C: u8>(_: &'t ()) -> impl use Sized { C } +// ^^^^^^^^^^^^^^^^^ +// ^ Captures `C` only. +``` + +## Capturing from outer inherent impl + +We can capture generic parameters from an outer inherent impl: + +```rust +struct Ty<'a, 'b>(&'a (), &'b ()); + +impl<'a, 'b> Ty<'a, 'b> { + fn foo(x: &'a (), _: &'b ()) -> impl use<'a> Sized { x } + // ^^^^^^^^^^^^^^^^^^ + // ^ Captures `'a` only. +} +``` + +## Capturing from outer trait impl + +We can capture generic parameters from an outer trait impl: + +```rust +trait Trait<'a, 'b> { + type Foo; + fn foo(_: &'a (), _: &'b ()) -> Self::Foo; +} + +impl<'a, 'b> Trait<'a, 'b> for () { + type Foo = impl use<'a> Sized; + // ^^^^^^^^^^^^^^^^^^ + // ^ Captures `'a` only. + fn foo(x: &'a (), _: &'b ()) -> Self::Foo { x } +} +``` + +## Capturing in trait definition + +We can capture generic parameters from the trait definition: + +```rust +trait Trait<'a, 'b> { + fn foo(_: &'a (), _: &'b ()) -> impl use<'a, Self> Sized; + // ^^^^^^^^^^^^^^^^^^^^^^^^ + // ^ Captures `'a` and `Self` only. +} +``` + +## Capturing elided lifetimes + +We can capture elided lifetimes: + +```rust +fn foo(x: &()) -> impl use<'_> Sized { x } +// ^^^^^^^^^^^^^^^^^^ +// ^ Captures `'_` only. +``` + +## Combining with `for<..>` + +The `use<..>` specifier applies to the entire `impl Trait` opaque type. In contrast, a `for<..>` binder applies to an individual *bound* within an opaque type. Therefore, when both are used within the same type, `use<..>` always appears first. E.g.: + +```rust +fn foo(_: T) -> impl use for<'a> FnOnce(&'a ()) { |&()| () } +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +## Syntax + +The [syntax for `impl Trait`][] is revised and extended as follows: + +[syntax for `impl Trait`]: https://doc.rust-lang.org/nightly/reference/types/impl-trait.html + +> _UseCaptures_ :\ +>    `use` [_GenericParams_][] +> +> _ImplTraitType_ : +>    `impl` _UseCaptures_? [_TypeParamBounds_][] +> +> _ImplTraitTypeOneBound_ : +>    `impl` _UseCaptures_? [_TraitBound_][] + +[_GenericParams_]: https://doc.rust-lang.org/nightly/reference/items/generics.html +[_TypeParamBounds_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html +[_TraitBound_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html + +## Reference desugaring + +Associated type position `impl Trait` (ATPIT) can also be used, more verbosely, to control capturing of generic parameters in opaque types. We can use this to describe the semantics of `use<..>`. If we consider the following code: + +```rust +struct Ty<'u, U, const CU: u8>(&'u (), U); + +impl<'u, U, const CU: u8> Ty<'u, U, CU> { + pub fn f<'t, T, const CT: u8>( + self, x: &'t (), y: T, + ) -> impl use<'u, 't, U, T, CU, CT> Sized { + (self, x, y, CU, CT) + } +} +``` + +Then, using ATPIT, we could desugar this as follows while preserving equivalent semantics: + +```rust +struct Ty<'u, U, const CU: u8>(&'u (), U); + +impl<'u, U, const CU: u8> Ty<'u, U, CU> { + pub fn f<'t, T, const CT: u8>( + self, x: &'t (), y: T, + ) -> <() as _0::H>::Opaque<'u, 't, U, T, CU, CT> { + <() as _0::H>::f(self, x, y) + } +} + +mod _0 { + use super::*; + pub trait H { + type Opaque<'u, 't, U, T, const CU: u8, const CT: u8>; + fn f<'u, 't, U, T, const CU: u8, const CT: u8>( + s: Ty<'u, U, CU>, x: &'t (), y: T, + ) -> Self::Opaque<'u, 't, U, T, CU, CT>; + } + impl H for () { + type Opaque<'u, 't, U, T, const CU: u8, const CT: u8> + = impl Sized; + #[inline(always)] + fn f<'u, 't, U, T, const CU: u8, const CT: u8>( + s: Ty<'u, U, CU>, x: &'t (), y: T, + ) -> Self::Opaque<'u, 't, U, T, CU, CT> { + (s, x, y, CU, CT) + } + } +} +``` + +## Avoiding capture of higher ranked lifetimes in nested opaques + +For implementation reasons, Rust does not yet support higher ranked lifetime bounds on nested opaque types (see [#104288][]). However, according to the Lifetime Capture Rules 2024, a nested `impl Trait` opaque type *must* capture all generic parameters in scope, including higher ranked ones. Therefore, in Rust 2024, this code fails to compile: + +```rust +trait Trait { type Ty; } +impl Trait for F { type Ty = (); } + +fn foo() -> impl for<'a> Trait { + //~^ ERROR `impl Trait` cannot capture higher-ranked lifetime + //~| from outer `impl Trait` + fn f(_: &()) -> &'static () { &() } + f +} +``` + +With `use<..>`, we can avoid capturing this higher ranked lifetime, allowing compilation: + +```rust +fn foo() -> impl for<'a> Trait Sized> { + // ^^^^^^^^^^^^^^^^ + // ^ Captures nothing. + fn f(_: &()) -> &'static () { &() } + f +} +``` + +[#104288]: https://github.com/rust-lang/rust/issues/104288 + +## Capturing higher ranked lifetimes in nested opaques + +Once higher ranked lifetime bounds on nested opaque types are supported in Rust (see [#104288][]), we'll be able to use `use<..>` specifiers to capture lifetime parameters from higher ranked `for<..>` binders on outer opaque types: + +```rust +trait Trait<'a> { type Ty; } +impl<'a, F: Fn(&'a ()) -> &'a ()> Trait<'a> for F { type Ty = &'a (); } + +fn foo() -> impl for<'a> Trait<'a, Ty = impl use<'a> Sized> { + // ^^^^^^^^^^^^^^^^^^ + // ^ Captures `'a`. + fn f(x: &()) -> &() { x } + f +} +``` + +## Refinement + +If we write a trait such as: + +```rust +trait Trait { + type Foo<'a>: Sized where Self: 'a; + fn foo(&self) -> Self::Foo<'_>; +} +``` + +...then an impl of this trait can provide a type for the associated type `Foo` that uses the `&'_ self` lifetime: + +```rust +struct A; +impl Trait for A { + type Foo<'a> = &'a Self; // Or, e.g.: `impl use<'a> Sized` + fn foo(&self) -> Self::Foo<'_> { self } +} +``` + +However, such an impl may also provide a type that does *not* use the lifetime: + +```rust +struct B; +impl Trait for B { + type Foo<'a> = (); // Or, e.g.: `impl use<> Sized` + fn foo(&self) -> Self::Foo<'_> {} +} +``` + +If we only know that the value is of some type that implements the trait, then we must assume that the type returned by `foo` *might* have used the lifetime: + +```rust +fn test_trait(x: T) -> impl Sized + 'static { + x.foo() +//~^ ERROR cannot return value referencing function parameter `x` +} +``` + +However, if we know we have a value of type `B`, we can *rely* on the fact that the lifetime was not used: + +```rust +fn test_b(x: B) -> impl Sized + 'static { + x.foo() //~ OK. +} +``` + +We would say that the impl for `B` is *refining* in that it offers more to or demands less of callers than the minimum the trait could offer or the maximum it could demand. Associated type definitions are always refining in this way. + +RPITIT desugars into associated types similar to those above, but here we've currently decided to lint against this refinement, e.g.: + +```rust +trait Trait { + fn foo(&self) -> impl Sized; +} + +impl Trait for () { + fn foo(&self) -> () {} +//~^ WARN impl trait in impl method signature does not match +//~| trait method signature +//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended +//~| for this to be part of the public API of this crate +//~| NOTE we are soliciting feedback, see issue #121718 +//~| +//~| for more information +} +``` + +Similarly, for consistency, we'll lint against RPITIT cases where less is captured by RPIT in the impl as compared with the trait definition when using `use<..>`. E.g.: + +```rust +trait Trait { + fn foo(&self) -> impl Sized; +} + +impl Trait for () { + fn foo(&self) -> impl use<> Sized {} +//~^ WARN impl trait in impl method signature does not match +//~| trait method signature +//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended +//~| for this to be part of the public API of this crate +//~| NOTE we are soliciting feedback, see issue #121718 +//~| +//~| for more information +} +``` + +## 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.: + +```rust +fn foo(x: impl Sized) -> impl use<> Sized { x } +// ^^^^^^^^^^^^^^^^ +// ^ Captures nothing. +``` + +## Migration strategy for Lifetime Capture Rules 2024 + +The migration lints for Rust 2024 will insert `use<..>` as needed so as to preserve the set of generic parameters captured by each RPIT opaque type. That is, we will convert, e.g., this: + +```rust +//@ edition: 2021 +fn foo<'t, T>(_: &'t (), x: T) -> impl Sized { x } +``` + +...into this: + +```rust +//@ edition: 2024 +fn foo<'t, T>(_: &'t (), x: T) -> impl use Sized { x } +``` + +Note that since generic type parameters must have names to be captured with `use<..>`, some uses of APIT will need to be converted to named generic parameters. E.g., we will convert this: + +```rust +//@ edition: 2021 +fn foo<'t>(_: &'t (), x: impl Sized) -> impl use Sized { x } +``` + +...into this: + +```rust +//@ edition: 2024 +fn foo<'t, T: Sized>(_: &'t (), x: T) -> impl use Sized { x } +``` + +As we're always cognizant of adding noise during migrations, it's worth mentioning that this will also allow noise to be *removed*. E.g., this code: + +```rust +#[doc(hidden)] +pub trait Captures<'t> {} +impl Captures<'_> for T {} + +pub fn foo<'a, 'b, 'c>( + x: &'a (), y: &'b (), _: &'c (), +) -> impl Sized + Captures<'a> + Captures<'b> { + (x, y) +} +``` + +...can be replaced with this: + +```rust +pub fn foo<'a, 'b, 'c>( + x: &'a (), y: &'b (), _: &'c (), +) -> impl use<'a, 'b> Sized { + (x, y) +} +``` + +As an example of what migrating to explicit `use<..>` captures looks like within `rustc` itself (without yet migrating to the Lifetime Capture Rules 2024 which would simplify many cases further), see [this diff][]. + +[this diff]: https://github.com/rust-lang/rust/compare/efd136e5cd57789834c7555eed36c490b7be6fe7...0d15c5c62d2a6f46269e5812653900e0945738bf?expand=1 + +## Stabilization strategy + +Due to implementation considerations, it's likely that the initial stabilization of this feature will be partial. We anticipate that partial stabilization will have these restrictions: + +- `use<..>`, if provided, must include all in-scope type and const generic parameters. +- In RPIT within trait definitions, `use<..>`, if provided, must include all in-scope generic parameters. + +We anticipate lifting these restrictions over time. + +Since all in-scope type and const generic parameters were already captured in Rust 2021 and earlier editions, and since RPITIT already adheres to the Lifetime Capture Rules 2024, these restrictions do not interfere with the use of this feature to migrate code to Rust 2024. + +# Alternatives +[alternatives]: #alternatives + +## ATPIT / TAIT + +As we saw in the reference desugaring above, associated type position `impl Trait` (ATPIT), once stabilized, can be used to effect precise capturing. Originally, we had hoped that this (particularly once expanded to full type alias `impl Trait` (TAIT)) might be sufficient and that syntax such as that in this RFC might not be necessary. + +As it turned out, there are four problems with this: + +1. ATPIT/TAIT is too indirect a solution. +2. They might not be stabilized in time. +3. They would lead to a worse migration story. +4. We would want this syntax anyway. + +Taking these in turn: + +One, as can be seen in the reference desugaring, using ATPIT/TAIT in this way can be rather indirect, and this was confirmed in our practical experience when migrating code. ATPIT and TAIT are good tools, but they weren't designed to solve this particular problem. This problem calls for a more direct solution. + +Two, while ATPIT is nearing stabilization, there are yet some type systems details being resolved. For TAIT, there is much work yet to do. Putting these features in the critical path would add risk to the edition, to the Lifetime Capture Rules 2024, and to these features. + +Three, as a practical matter, an explicit `impl use<..> Trait` syntax lets us write much better automatic migration lints and offers a much more straightforward migration story for our users. + +Four, the set of generic parameters that are captured by an opaque type is a fundamental and practical property of that opaque type. In a language like Rust, it *feels* like there ought to be an explicit syntax for it. We probably want this in any world. + +## Inferred precise capturing + +We had hoped that we might be able to achieve something with a similar effect to precise capturing at the cost of an extra generic lifetime parameter in each signature with improvements to the type system. The goal would be to allow, e.g., this code to work rather than error: + +```rust +fn foo<'o, T>(_: T) -> impl Sized + 'o {} + +fn bar(x: ()) -> impl Sized + 'static { + foo(&x) +//~^ ERROR returns a value referencing data owned by the +//~| current function +} +``` + +The idea is that, even though the opaque type returned by `foo` does capture the generic type parameter `T`, since the opaque type is explicitly bounded by `'o` and the signature does not assert `T: 'o`, we know that the hidden type cannot actually use `T`. + +As it turns out, making full use of this observation is challenging (see [#116040][] and [#116733][]). While we did make improvements to the type system here, and while more might be possible, this does not solve the problem today in all important cases (including, e.g., avoiding the capture of higher ranked lifetimes in nested opaque types) and will not for the foreseeable future. + +Moreover, even with the fullest possible version of these improvements, whether or not a generic parameter is captured by an opaque type would remain observable. Having an explicit syntax to control what is captured is more direct, more expressive, and leads to a better migration story. + +See [Appendix G][] in [RFC 3498][] for more details. + +[#116040]: https://github.com/rust-lang/rust/pull/116040 +[#116733]: https://github.com/rust-lang/rust/pull/116733 +[Appendix G]: https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md#appendix-g-future-possibility-inferred-precise-capturing + +## Syntax + +We considered a number of different possible syntaxes before landing on `impl use<..> Trait`. We'll discuss each considered. + +### `impl use<..> Trait` + +This is the syntax chosen in this RFC. + +Using a separate keyword makes this syntax more scalable in the sense that we can apply `use<..>` in other places. + +Conveniently, the word "use" is quite appropriate here, since we are *using* the generic parameters in the type of the opaque type and allowing the generic parameters to be *used* in the hidden type. + +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. + +Using angle brackets, rather than parenthesis or square brackets, is consistent with other places in the language where type parameters are applied to a type. + +At three letters, the `use` keyword is short enough that it doesn't feel too noisy or too much like a burden to use this, and it's parsimonious with other short keywords in Rust. + +Overall, naming is hard, but on average, people seemed to dislike this choice the least. + +### `impl<..> Trait` + +The original syntax proposal was `impl<..> Trait`. This has the benefit of being somewhat more concise than `impl use<..> Trait` but has the drawback of perhaps suggesting that it's introducing generic parameters as other uses of `impl<..>` do. Many preferred to use a different keyword for this reason. + +Decisive to some was that we may want this syntax to *scale* to other uses, most particularly to controlling the set of generic parameters and values that are captured by closure-like blocks. As we discuss in the future possibilities, it's easy to see how `use<..>` can scale to address this in a way that `impl<..> Trait` cannot. + +### `impl Trait & ..` + +In some conceptions, the difference between `impl Trait + 'a + 'b` and `impl use<'a, 'b> Trait` is the difference between capturing the union of those lifetimes and capturing the intersection of them. This inspires syntax proposals such as `impl Trait & 't & T` or `impl Trait & ['t, T]` to express this intersection. + +One problem with the former of these is that it gives no obvious way to express that the opaque type captures nothing. Another is that it would give `AsRef &T` a valid but distinct meaning to `AsRef<&T>` which might be confusing. + +For either of these, appearing later in the type would put these after higher ranked `for<..>` lifetimes may have been introduced. This could be confusing, since `use<..>` (with any syntax) captures generic parameters for the entire type where `for<..>` applies individually to each bound. + +Overall, nobody seemed to like this syntax. + +### `impl k#captures<..> Trait` + +We could use a new and very literal keyword such as `captures` rather than `use`. There are three main drawbacks to this: + +1. There are limits to how this could be used in older editions. +2. There's a cost to each new keyword, and `use` is probably good enough. +3. It's somewhat long. + +Taking these in turn: + +One, while `captures` could be reserved in Rust 2024 and used in any position in that edition, and in Rust 2021 could be used as `k#captures` in any position, on older editions, it would only be able to be used where it could be made contextual. This could limit how we might be able to scale this syntax to handle other use cases such as controlling the capturing of generic parameters and values in closure-like blocks (as discussed in the future possibilities). + +Two, each keyword takes from the space of names that users have available to them, and it increases the number of keywords with which users must be familiar (e.g. so as to not inadvertently trip over when choosing a name). That is, each keyword has a cost. If an existing keyword can reasonably be used in more places, then we get more benefit for that cost. In this case, `use` is probably a strong enough choice that paying the cost for a new keyword doesn't seem worth it. + +Three, `captures` would be a somewhat long keyword, especially when we consider how we might scale the use of this syntax to other places such as closure-like blocks. We don't want people to feel punished for being explicit about the generics that they capture, and we don't want them to do other worse things (such as overcapturing where they should not) just to avoid visual bloat in their code, so if we can be more concise here, that seems like a win. + +### `impl move<'t, T> Trait` + +We could use the existing `move` keyword, however the word "move" is semantically worse. In Rust, we already *use* generic parameters in types, but we don't *move* any generic parameters. We move only *values*, so this could be confusing. The word "use" is better. + +### `impl k#via<'t, T> Trait` + +We could use a new short keyword such as `via`. This has the number 1 and 2 drawbacks of `k#captures` mentioned above. As with `move`, it also seems a semantically worse word. With `use<..>`, we can explain that it means the opaque type *uses* the listed generic parameters. In contrast, it's not clear how we could explain the word "via" in this context. + +### Using parenthesis or square brackets + +We could say `use('t, T)` or `use['t, T]`. However, in Rust today, generic parameters always fall within angle brackets, even when being applied to a type. Doing something different here could feel inconsistent and doesn't seem warranted. + +# Future possibilities +[future-possibilities]: #future-possibilities + +## Opting out of captures + +There will plausibly be cases where we want to capture many generic parameters and not capture only smaller number. It could be convenient if there were a way to express this without listing out all of the in-scope type parameters except the ones not being captured. + +The way we would approach this with the `use<..>` syntax is to add some syntax that means "fill in all in-scope generic parameters", then add syntax to remove certain generic parameters from the list. E.g.: + +```rust +fn foo<'a, A, B, C, D>( + _: &'a A, b: B, c: C, d: D, +) -> impl use<.., !'a, !A> Sized {} +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +// ^ 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. + +We leave this to future work. + +## Explicit capturing for closure-like blocks + +Closures and closure-like blocks (e.g. `async`, `gen`, `async gen`, `async` closures, `gen` closures, `async gen` closures, etc.) return opaque types that capture both *values* and *generic parameters* from the outer scope. + +### Specifying captured generics for closures-like blocks + +The capturing of outer generics in closure-like blocks can lead to overcapturing, as in [#65442][]. Consider: + +```rust +trait Trait { + type Ty; + fn define(_: T) -> Self::Ty; +} + +impl Trait for () { + type Ty = impl Fn(); + fn define(_: T) -> Self::Ty { + || () + //~^ ERROR type parameter `T` is part of concrete type but not + //~| used in parameter list for the `impl Trait` type alias + } +} +``` + +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.: + +```rust +trait Trait { + type Ty; + fn define(_: T) -> Self::Ty; +} + +impl Trait for () { + type Ty = impl Fn(); + fn define(_: T) -> Self::Ty { + use<> || () + // ^^^^^^^^^^^ + // ^ Captures no generic parameters. + } +} +``` + +We leave this to future work, but this demonstrates how the `use<..>` syntax can scale to solve other problems. + +[#65442]: https://github.com/rust-lang/rust/issues/65442 + +### Specifying captured values for closure-like blocks + +Closure-like blocks capture values either by *moving* them or by *referencing* them. How Rust decides whether values should be captured by move or by reference is implicit and can be a bit subtle. E.g., this works: + +```rust +fn foo(x: T) -> impl FnOnce() -> T { + || x +} +``` + +...but this does not: + +```rust +fn foo(x: T) -> impl FnOnce() -> T { + || x +//~^ ERROR may outlive borrowed value `x` +} +``` + +While in simple cases like this we can apply `move` to the entire closure-like block to get the result that we want, in other cases other techniques are needed. + +We might want a syntax for specifying which values are captured by the closure-like block and how each value is captured. We could apply the `use` syntax to solve this. E.g.: + +```rust +fn foo(a: A, b: B, mut c: C, _: D) { + let f = use(a, ref b, ref mut c) || { + // ^ ^^^^^ ^^^^^^^^^ + // | | ^ Captures `c` by mutable reference. + // | ^ Captures `b` by immutable reference. + // ^ Captures `a` by move. + todo!() + } + todo!() +} +``` + +This could be combined with specifying which outer generic parameters to capture, e.g. with `use(a, ref b, ref mut c)`. + +We leave this to future work, but this demonstrates how the `use<..>` syntax can scale to solve other problems. From 312f5a14074fa483ded6105981c459c3ef42cd4a Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 24 Apr 2024 14:21:28 +0000 Subject: [PATCH 02/17] Add observation about `use` bringing things into scope 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. --- text/3617-precise-capturing.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 4d9049a66ae..6d52f12fb0e 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -64,6 +64,8 @@ We need some way to migrate this kind of code. In all editions, RPIT-like `impl Trait` opaque types may include `use<..>` in the bound to specify which in-scope generic parameters are captured or that no in-scope generic parameters are captured (with `use<>`). If `use<..>` is provided, it entirely overrides the implicit rules for which generic parameters are captured. +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 example, we can solve the overcapturing in the original motivating example by writing: ```rust @@ -504,7 +506,7 @@ This is the syntax chosen in this RFC. Using a separate keyword makes this syntax more scalable in the sense that we can apply `use<..>` in other places. -Conveniently, the word "use" is quite appropriate here, since we are *using* the generic parameters in the type of the opaque type and allowing the generic parameters to be *used* in the hidden type. +Conveniently, the word "use" is quite appropriate here, since we are *using* the generic parameters in the type of the opaque type and allowing the generic parameters to be *used* in the hidden type. That is, with `use`, we are bringing the generic parameters *into scope* for the hidden type, and `use` is the keyword in Rust for bringing things into scope. 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. From 585458cc82e124610ed721569b42bb50e26d6669 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 24 Apr 2024 14:24:17 +0000 Subject: [PATCH 03/17] Remove stray `use` We had included one `use` 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.) --- text/3617-precise-capturing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 6d52f12fb0e..a3086d4a782 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -397,7 +397,7 @@ Note that since generic type parameters must have names to be captured with `use ```rust //@ edition: 2021 -fn foo<'t>(_: &'t (), x: impl Sized) -> impl use Sized { x } +fn foo<'t>(_: &'t (), x: impl Sized) -> impl Sized { x } ``` ...into this: From dc8f93d315cb44573fa6edf583f43f5741ceb0a2 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 24 Apr 2024 21:48:15 +0000 Subject: [PATCH 04/17] Rework the syntax specification 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.) --- text/3617-precise-capturing.md | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index a3086d4a782..61db4d86b24 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -171,18 +171,30 @@ The [syntax for `impl Trait`][] is revised and extended as follows: [syntax for `impl Trait`]: https://doc.rust-lang.org/nightly/reference/types/impl-trait.html -> _UseCaptures_ :\ ->    `use` [_GenericParams_][] -> > _ImplTraitType_ : >    `impl` _UseCaptures_? [_TypeParamBounds_][] > > _ImplTraitTypeOneBound_ : >    `impl` _UseCaptures_? [_TraitBound_][] +> +> _UseCaptures_ :\ +>    `use` _UseCapturesGenericArgs_ +> +> _UseCapturesGenericArgs_ :\ +>       `<` `>` \ +>    | `<` \ +>       ( _UseCapturesGenericArg_ `,`)\* \ +>       _UseCapturesGenericArg_ `,`? \ +>       `>` +> +> _UseCapturesGenericArg_ :\ +>       [LIFETIME_OR_LABEL][] \ +>    | [IDENTIFIER][] -[_GenericParams_]: https://doc.rust-lang.org/nightly/reference/items/generics.html -[_TypeParamBounds_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html +[IDENTIFIER]: https://doc.rust-lang.org/nightly/reference/identifiers.html +[LIFETIME_OR_LABEL]: https://doc.rust-lang.org/nightly/reference/tokens.html#lifetimes-and-loop-labels [_TraitBound_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html +[_TypeParamBounds_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html ## Reference desugaring From 76cb0ae10cb210aecbbd58b7c32237898deedebc Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 24 Apr 2024 23:03:53 +0000 Subject: [PATCH 05/17] Discuss the `use<..> impl Trait` alternative 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. --- text/3617-precise-capturing.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 61db4d86b24..f57c7408b23 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -524,6 +524,8 @@ Picking an existing keyword allows for this syntax, including extensions to othe 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. +We put `impl` before `use<..>` because `use<..>` is a property of the opaque type and we're *applying* the generic *parameters* as generic *arguments* to this opaque type. In `impl Trait` syntax, the `impl` keyword is the stand-in for the opaque type itself. Viewed this way, `impl use<..> Trait` maintains the following order, which is seen throughout Rust: *type*, *generic arguments*, *bounds*. + Using angle brackets, rather than parenthesis or square brackets, is consistent with other places in the language where type parameters are applied to a type. At three letters, the `use` keyword is short enough that it doesn't feel too noisy or too much like a burden to use this, and it's parsimonious with other short keywords in Rust. @@ -536,6 +538,36 @@ The original syntax proposal was `impl<..> Trait`. This has the benefit of bein Decisive to some was that we may want this syntax to *scale* to other uses, most particularly to controlling the set of generic parameters and values that are captured by closure-like blocks. As we discuss in the future possibilities, it's easy to see how `use<..>` can scale to address this in a way that `impl<..> Trait` cannot. +### `use<..> impl Trait` + +Putting the `use<..>` specifier *before* the `impl` keyword is potentially appealing as `use<..>` applies to the entire `impl Trait` opaque type rather than to just one of the bounds, and this ordering might better suggest that. + +However, this visual association might also *prove too much*. That is, it could make the `use<..>` look more like a *binder* (like `for<..>`) rather than like a *property* of the opaque type. + +The `use<..>` syntax *applies* the listed generic *parameters* as generic *arguments* to the opaque type. It's analogous, e.g., with the generic arguments here: + +```rust +impl Trait for () { + type Opaque<'t, T> = Concrete<'t, T> + // ^^^^^^^^|^^^^^ + // Type | Generic Arguments + where Self: 'static; + // ^^^^^^^^^^^^^ + // Bounds +} +``` + +Just as the above *applies* `<'t, T>` to `Concrete`, `use<..>` applies its arguments to the opaque type. + +In the above example and throughout Rust, we observe the following order: *type*, *generic arguments* (applied to the type), *bounds*. In `impl Trait` syntax, the `impl` keyword is the stand-in for the opaque type itself. The `use<..>` specifier lists the generic arguments to be applied to that type. Then the bounds follow. Putting `use<..>` after `impl` is consistent with this rule, but the other way would be inconsistent. + +This observation, that we're applying generic *arguments* to the opaque type and that the `impl` keyword is the stand-in for that type, is also a strong argument in favor of `impl<..> Trait` syntax. It's conceivable that we'll later, with more experience and consistently with [Stroustrup's Rule][], decide that we want to be more concise and adopt the `impl<..> Trait` syntax after all. One of the advantages of placing `use<..>` after `impl` is that there would be less visual and conceptual churn in later making that change. + +Finally, there's one other practical advantage to placing `impl` before `use<..>`. If we were to do it the other way and place `use<..>` before `impl`, we would need to make a backward incompatible change to the `ty` macro matcher fragment specifier. This would require us to migrate this specifier according to our policy in [RFC 3531][]. This is something we could do, but it is a cost on us and on our users, and combined with the other good reasons that argue for `impl use<..> Trait` (or even `impl<..> Trait`), it doesn't seem a cost that's worth paying. + +[RFC 3531]: https://github.com/rust-lang/rfcs/blob/master/text/3531-macro-fragment-policy.md +[Stroustrup's Rule]: https://www.thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/ + ### `impl Trait & ..` In some conceptions, the difference between `impl Trait + 'a + 'b` and `impl use<'a, 'b> Trait` is the difference between capturing the union of those lifetimes and capturing the intersection of them. This inspires syntax proposals such as `impl Trait & 't & T` or `impl Trait & ['t, T]` to express this intersection. From 26911c8b30f7ece1415bba2779ea11b5ce9156a1 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 24 Apr 2024 23:36:57 +0000 Subject: [PATCH 06/17] Fix plurality of "parentheses" We had meant to say "parentheses" but had said "parenthesis" in two places. Let's fix that. --- text/3617-precise-capturing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index f57c7408b23..146e222e0c3 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -526,7 +526,7 @@ By not putting the generic parameters on `impl<..>`, we reduce the risk of confu We put `impl` before `use<..>` because `use<..>` is a property of the opaque type and we're *applying* the generic *parameters* as generic *arguments* to this opaque type. In `impl Trait` syntax, the `impl` keyword is the stand-in for the opaque type itself. Viewed this way, `impl use<..> Trait` maintains the following order, which is seen throughout Rust: *type*, *generic arguments*, *bounds*. -Using angle brackets, rather than parenthesis or square brackets, is consistent with other places in the language where type parameters are applied to a type. +Using angle brackets, rather than parentheses or square brackets, is consistent with other places in the language where type parameters are applied to a type. At three letters, the `use` keyword is short enough that it doesn't feel too noisy or too much like a burden to use this, and it's parsimonious with other short keywords in Rust. @@ -602,7 +602,7 @@ We could use the existing `move` keyword, however the word "move" is semanticall We could use a new short keyword such as `via`. This has the number 1 and 2 drawbacks of `k#captures` mentioned above. As with `move`, it also seems a semantically worse word. With `use<..>`, we can explain that it means the opaque type *uses* the listed generic parameters. In contrast, it's not clear how we could explain the word "via" in this context. -### Using parenthesis or square brackets +### Using parentheses or square brackets We could say `use('t, T)` or `use['t, T]`. However, in Rust today, generic parameters always fall within angle brackets, even when being applied to a type. Doing something different here could feel inconsistent and doesn't seem warranted. From 9fbce80d84f3ad75b71ed0d08dd7d1528ef6280f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 26 Apr 2024 07:23:25 +0000 Subject: [PATCH 07/17] Add guide-level section on the trailing comma As with other lists of generic arguments in Rust, the grammar for `use<..>` specifiers supports an optional trailing comma. This is already specified in the formal grammar, but let's also note this in the guide-level section. (Thanks to Josh Triplett for raising this point.) --- text/3617-precise-capturing.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 146e222e0c3..16b5c5e91fd 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -162,6 +162,15 @@ The `use<..>` specifier applies to the entire `impl Trait` opaque type. In cont fn foo(_: T) -> impl use for<'a> FnOnce(&'a ()) { |&()| () } ``` +## Optional trailing comma + +As with other lists of generic arguments in Rust, a trailing comma is optional in `use<..>` specifiers: + +```rust +fn foo1(_: T) -> impl use Sized {} //~ OK. +fn foo2(_: T) -> impl use Sized {} //~ Also OK. +``` + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From f01222877d13ad685b6288ca5c51d0a446a5cf31 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 27 Apr 2024 08:14:39 +0000 Subject: [PATCH 08/17] Extend discussion on `use<..> impl Trait` syntax We had earlier written up a section on `use<..> impl Trait` syntax that mostly focused on why we had not adopted it. We didn't spend much text on why it's appealing, probably because we are in fact sympathetic to it and consider the reasons that it's appealing obvious. Still, we should write all those reasons down. Let's extend the section on this syntax with the best possible argument in favor. We also see more clearly now the fundamental intuitive tension behind this syntax and `impl use<..> Trait`, so let's write that down too. Finally, let's describe the historical and other factors that led to picking one syntax over the other. (Thanks to tmandry for suggesting the `use<..> impl Trait` syntax and many of the arguments in favor of it.) --- text/3617-precise-capturing.md | 110 +++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 6 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 16b5c5e91fd..9fe44afb1dd 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -551,18 +551,76 @@ Decisive to some was that we may want this syntax to *scale* to other uses, most Putting the `use<..>` specifier *before* the `impl` keyword is potentially appealing as `use<..>` applies to the entire `impl Trait` opaque type rather than to just one of the bounds, and this ordering might better suggest that. -However, this visual association might also *prove too much*. That is, it could make the `use<..>` look more like a *binder* (like `for<..>`) rather than like a *property* of the opaque type. +Let's discuss some arguments for this, some arguments against it, and then discuss the fundamental tension here. -The `use<..>` syntax *applies* the listed generic *parameters* as generic *arguments* to the opaque type. It's analogous, e.g., with the generic arguments here: +#### The case for `use<..>` before `impl` + +We've been referring to the syntax for RPIT-like opaque types as `impl Trait`, as is commonly done. But this is a bit imprecise. The syntax is really `impl $bounds`. We might say, e.g.: + +```rust +fn foo() -> impl 'static + Unpin + for<'a> FnMut(&'a ()) { + |_| () +} +``` + +Each *bound*, separated by `+`, may be a *lifetime* or a *trait bound*. Each trait bound may include a higher ranked `for<..>` *binder*. The lifetimes introduced in such a binder are in scope only for the bound in which that binder appears. + +This could create confusion with `use<..>` after `impl`. If we say, e.g.: + +```rust +fn foo<'a>( + _: &'a (), +) -> impl use<'a> for<'b> FnMut(&'b ()) + for<'c> Trait<'c> { + // ^^^^^^^ ^^^^^^^ ^^^^^^^ + // | | ^ Applies to one bound. + // | ^ Applies to one bound. + // ^ Applies to the whole type. + |_| () +} +``` + +...then it may feel like `use<..>` should apply to only the first bound, just as the `for<..>` binder right next to it does. Putting `use<..>` *before* `impl` might avoid this issue. E.g.: + +```rust +fn foo<'a>( + _: &'a (), +) -> use<'a> impl for<'b> FnMut(&'b ()) + for<'c> Trait<'c> { + |_| () +} +``` + +This would make it clear that `use<..>` applies to the entire type. This seems the strongest argument for putting `use<..>` before `impl`, and it's a *good* one. + +#### The case for and against `use<..>` before `impl` + +There are some other known arguments for this ordering that may or may not resonate with the reader; we'll present these, along with the standard arguments that might be made in response, as an imagined conversation between Alice and Bob: + +> **Bob**: We call the base feature here "`impl Trait`". Anything that we put between the `impl` and the `Trait` could make this less recognizable to people. +> +> **Alice**: Maybe, but users don't literally write the words `impl Trait`; they write `impl` and then a set of bounds. They could even write `impl 'static + Fn()`, e.g. The fact that there can be multiple traits and that a lifetime or a `for<..>` binder could come between the `impl` and the first trait doesn't seem to be a problem here, so maybe adding `use<..>` won't be either. +> +> **Bob**: But what about the orthography? In English, we might say "using 'x, we implement the trait". We'd probably try to avoid saying "we implement, using 'x, the trait". Putting `use<..>` first better lines up with this. +> +> **Alice**: Is that true? Would we always prefer the first version? To my ears, "using 'x, we implement the trait" sounds a bit like something Yoda would say. I'd probably say the second version, if I had to choose. Really, of course, I'd mostly try to say instead that "we implement the trait using 'x", but there are probably good reasons to not use that ordering here in Rust. +> +> **Bob**: The RFC talks about maybe later extending the `use<..>` syntax to closure-like blocks, e.g. `use<> |x| x`. If it makes sense to put the `use<..>` first here, shouldn't we put it first in `use<..> impl Trait`? +> +> **Alice**: That's interesting to think about. In the case of closure-like blocks, we'd probably want to put the `use<..>` in the same position as `move` as it could be extended to serve a similar purpose. For closures, that would mean putting it before the arguments, e.g. `use<> |x| x`, just as we do with `move`. But this would also imply that `use<..>` should appear *after* certain keywords, e.g. for `async` blocks we currently write `async move {}`, so maybe here we would write `async use<> {}`. +> +> **Alice**: There is a key difference to keep in mind here. Closure-like blocks are *expressions* but `impl Trait` is syntax for a *type*. We often have different conventions between type position and expression position in Rust. Maybe (or maybe not) this is a place where that distinction could matter. + +#### The case against `use<..>` before `impl` + +The `use<..>` specifier syntax *applies* the listed generic *parameters* as generic *arguments* to the opaque type. It's analogous, e.g., with the generic arguments here: ```rust impl Trait for () { type Opaque<'t, T> = Concrete<'t, T> - // ^^^^^^^^|^^^^^ - // Type | Generic Arguments + // ^^^^^^^^ ^^^^^ + // ^ Type ^ Generic arguments where Self: 'static; // ^^^^^^^^^^^^^ - // Bounds + // ^ Bounds } ``` @@ -572,11 +630,51 @@ In the above example and throughout Rust, we observe the following order: *type* This observation, that we're applying generic *arguments* to the opaque type and that the `impl` keyword is the stand-in for that type, is also a strong argument in favor of `impl<..> Trait` syntax. It's conceivable that we'll later, with more experience and consistently with [Stroustrup's Rule][], decide that we want to be more concise and adopt the `impl<..> Trait` syntax after all. One of the advantages of placing `use<..>` after `impl` is that there would be less visual and conceptual churn in later making that change. -Finally, there's one other practical advantage to placing `impl` before `use<..>`. If we were to do it the other way and place `use<..>` before `impl`, we would need to make a backward incompatible change to the `ty` macro matcher fragment specifier. This would require us to migrate this specifier according to our policy in [RFC 3531][]. This is something we could do, but it is a cost on us and on our users, and combined with the other good reasons that argue for `impl use<..> Trait` (or even `impl<..> Trait`), it doesn't seem a cost that's worth paying. +Finally, there's one other practical advantage to placing `impl` before `use<..>`. If we were to do it the other way and place `use<..>` before `impl`, we would need to make a backward incompatible change to the `ty` macro matcher fragment specifier. This would require us to migrate this specifier according to our policy in [RFC 3531][]. This is something we could do, but it is a cost on us and on our users, even if only a modest one, and combined with the other good reasons that argue for `impl use<..> Trait` (or even `impl<..> Trait`), it doesn't seem a cost that's worth paying. [RFC 3531]: https://github.com/rust-lang/rfcs/blob/master/text/3531-macro-fragment-policy.md [Stroustrup's Rule]: https://www.thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/ +#### The fundamental tension on `impl use<..>` vs. `use<..> impl` + +Throughout this RFC, we've given two intuitions for the semantics of `use<..>`: + +- **Intuition #1**: `use<..>` *applies* generic arguments to the opaque type. +- **Intuition #2**: `use<..>` brings generic parameters *into scope* for the hidden type. + +These are *both* true and are both valid *intuitions*, but there's some tension between these for making this syntax choice. + +It's often helpful to think of `impl Trait` in terms of generic associated types (GATs), and let's make that analogy here. Consider: + +```rust +impl Trait for () { + type Opaque<'t, T> = Concrete<'t, T>; + // ^^^^^^ ^^^^^ ^^^^^^^^ ^^^^^ + // | | | ^ Generic arguments applied + // | | ^ Concrete type + // | ^ Generic parameters introduced into scope + // ^ Alias type (similar to an opaque type) + fn foo(&self) -> Self::Opaque<'_, T> { todo!() } + // ^^^^^^^^^^^^ ^^^^^ + // ^ Alias type ^ Generic arguments applied +} +``` + +The question is, are the generics in `use<..>` more like the generic *parameters* or more like the generic *arguments* above? + +If these generics are more like the generic *arguments* above (*Intuition #1*), then `impl<..> Trait` and `impl use<..> Trait` make a lot of sense as we're *applying* these arguments to the type. In Rust, when we're applying generic arguments to a type, the generic arguments appear *after* the type, and `impl` is the stand-in for the type here. + +However, if these generics are more like the generic *parameters* above (*Intuition #2*), then `use<..> impl Trait` makes more sense. In Rust, when we're putting generic parameters into scope, they appear before the type. + +Since both intuitions are valid, but each argues for a different syntax choice, picking one is tough. The authors are sympathetic to both choices. The key historical and tiebreaker factors leading to our choice of the `impl use<..> Trait` syntax in this RFC are: + +- The original longstanding and motivating semantic intuition for this feature was *Intuition #1*, and it argues for this syntax. The second intuition, *Intuition #2*, was only developed in the process of writing this RFC and after most of this RFC had been written. +- The `use<..> impl Trait` syntax was never proposed before this RFC was written (it may have been inspired by the presentation in this RFC of the second intuition), and in discussion, no clear consensus has yet emerged in its favor. +- There are some practical costs that exist for `use<..> impl Trait` that don't for `impl use<..> Trait`. +- The "obvious" syntax for this feature is `impl<..> Trait`. We may yet someday want to switch to this, and migrating from `impl use<..> Trait` seems like a smaller step. + +We are *not* leaving this as an open question, because given that there have already been substantial and productive discussions on this topic, and given that it's a bit of a coin flip where we're likely to be happy at the end of the day with either choice, it seems better to just pick one. But all questions are in some sense open until stabilization, if feelings shift far enough and an alternate consensus emerges, and the authors hope that people will take the opportunity to experiment with and experience the syntax on nightly. + ### `impl Trait & ..` In some conceptions, the difference between `impl Trait + 'a + 'b` and `impl use<'a, 'b> Trait` is the difference between capturing the union of those lifetimes and capturing the intersection of them. This inspires syntax proposals such as `impl Trait & 't & T` or `impl Trait & ['t, T]` to express this intersection. From b02f1aabccce80fd6143b43561b0b5c6ad0f82d7 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 28 Apr 2024 03:47:30 +0000 Subject: [PATCH 09/17] Improve example on avoiding HR lifetime capture We have an example showing how to avoid capturing a higher ranked lifetime in a nested opaque type so that the code can be migrated to Rust 2024. However, because we didn't parameterize the trait in the example with a lifetime, the code could be migrated by just removing the `for<..>` binder. This makes the example weaker than it could be, so let's strengthen it by parameterizing the trait. (Thanks to aliemjay for raising this point.) --- text/3617-precise-capturing.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 9fe44afb1dd..dcf9bc05934 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -257,13 +257,14 @@ mod _0 { ## Avoiding capture of higher ranked lifetimes in nested opaques -For implementation reasons, Rust does not yet support higher ranked lifetime bounds on nested opaque types (see [#104288][]). However, according to the Lifetime Capture Rules 2024, a nested `impl Trait` opaque type *must* capture all generic parameters in scope, including higher ranked ones. Therefore, in Rust 2024, this code fails to compile: +According to the Lifetime Capture Rules 2024, a nested `impl Trait` opaque type *must* capture all generic parameters in scope, including higher ranked ones. However, for implementation reasons, Rust does not yet support higher ranked lifetime bounds on nested opaque types (see [#104288][]). Therefore, in Rust 2024, this code, which is valid in Rust 2021, fails to compile: ```rust -trait Trait { type Ty; } -impl Trait for F { type Ty = (); } +//@ edition: 2024 +trait Trait<'a> { type Ty; } +impl Trait<'_> for F { type Ty = (); } -fn foo() -> impl for<'a> Trait { +fn foo() -> impl for<'a> Trait<'a, Ty = impl Sized> { //~^ ERROR `impl Trait` cannot capture higher-ranked lifetime //~| from outer `impl Trait` fn f(_: &()) -> &'static () { &() } @@ -274,9 +275,9 @@ fn foo() -> impl for<'a> Trait { With `use<..>`, we can avoid capturing this higher ranked lifetime, allowing compilation: ```rust -fn foo() -> impl for<'a> Trait Sized> { - // ^^^^^^^^^^^^^^^^ - // ^ Captures nothing. +fn foo() -> impl for<'a> Trait<'a, Ty = impl use<> Sized> { + // ^^^^^^^^^^^^^^^^ + // ^ Captures nothing. fn f(_: &()) -> &'static () { &() } f } From 972be3401ae24dc0b2888961878c1320acf35a40 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 28 Apr 2024 06:33:57 +0000 Subject: [PATCH 10/17] Extend and improve reference desugarings We had included a reference desugaring of `use<..>` in RPIT using ATPIT. Let's add a similar desugaring for `use<..>` in RPITIT. In doing this, we'll make some changes to the RPIT desugaring so that it better parallels the RPITIT one. In particular, we want to turbofish all generic parameters for clarity, and we want to eliminate all function arguments for conciseness. Doing this means that all of the lifetimes become early bound. This seems fine, since it's rather orthogonal to the semantics we're trying to demonstrate here. We also want to demonstrate using const generics in the hidden type. We could do this using arrays, e.g. `[(); N]`, but it seems more clear to just define a type constructor that uses all of the generics, so we'll sacrifice a bit of conciseness to do it that way. --- text/3617-precise-capturing.md | 184 +++++++++++++++++++++++++++++---- 1 file changed, 162 insertions(+), 22 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index dcf9bc05934..2595949435d 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -205,51 +205,191 @@ The [syntax for `impl Trait`][] is revised and extended as follows: [_TraitBound_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html [_TypeParamBounds_]: https://doc.rust-lang.org/nightly/reference/trait-bounds.html -## Reference desugaring +## Reference desugarings -Associated type position `impl Trait` (ATPIT) can also be used, more verbosely, to control capturing of generic parameters in opaque types. We can use this to describe the semantics of `use<..>`. If we consider the following code: +The desugarings that follow can be used to answer questions about how `use<..>` is expected to work with respect to the capturing of generic parameters in opaque types. + +### Reference desugaring for `use<..>` in RPIT + +Associated type position `impl Trait` (ATPIT) can be used, more verbosely, to control capturing of generic parameters in opaque types. We can use this to describe the semantics of `use<..>`. If we consider the following code: ```rust -struct Ty<'u, U, const CU: u8>(&'u (), U); +use core::marker::PhantomData; -impl<'u, U, const CU: u8> Ty<'u, U, CU> { +struct C<'s, 't, S, T, const CS: u8, const CT: u8> { + _p: PhantomData<(&'s (), &'t (), S, T)>, +} + +struct Ty<'s, S, const CS: u8>(&'s (), S); +impl<'s, S, const CS: u8> Ty<'s, S, CS> { pub fn f<'t, T, const CT: u8>( - self, x: &'t (), y: T, - ) -> impl use<'u, 't, U, T, CU, CT> Sized { - (self, x, y, CU, CT) + ) -> impl use<'s, 't, S, T, CS, CT> Sized { + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + // This is the `use<..>` specifier to desugar. + C::<'s, 't, S, T, CS, CT> { _p: PhantomData } } } ``` -Then, using ATPIT, we could desugar this as follows while preserving equivalent semantics: +Then we can desugar this as follows, without the use of a `use<..>` specifier, while preserving equivalent semantics with respect to the capturing of generic parameters: ```rust -struct Ty<'u, U, const CU: u8>(&'u (), U); +use core::marker::PhantomData; -impl<'u, U, const CU: u8> Ty<'u, U, CU> { +struct C<'s, 't, S, T, const CS: u8, const CT: u8> { + _p: PhantomData<(&'s (), &'t (), S, T)>, +} + +struct Ty<'s, S, const CS: u8>(&'s (), S); +impl<'s, S, const CS: u8> Ty<'s, S, CS> { pub fn f<'t, T, const CT: u8>( - self, x: &'t (), y: T, - ) -> <() as _0::H>::Opaque<'u, 't, U, T, CU, CT> { - <() as _0::H>::f(self, x, y) + ) -> <() as _0::H>::Opaque<'s, 't, S, T, CS, CT> { + // ^^^^^^^^^^^^^^^^^^^^ + // These are the arguments given to the `use<..>` specifier. + // + // Reducing what is captured by removing arguments from + // `use<..>` is equivalent to removing arguments from this + // list and as needed below. + <() as _0::H>::f::<'s, 't, S, T, CS, CT>() } } mod _0 { use super::*; pub trait H { - type Opaque<'u, 't, U, T, const CU: u8, const CT: u8>; - fn f<'u, 't, U, T, const CU: u8, const CT: u8>( - s: Ty<'u, U, CU>, x: &'t (), y: T, - ) -> Self::Opaque<'u, 't, U, T, CU, CT>; + type Opaque<'s, 't, S, T, const CS: u8, const CT: u8>; + fn f<'s, 't, S, T, const CS: u8, const CT: u8>( + ) -> Self::Opaque<'s, 't, S, T, CS, CT>; } impl H for () { - type Opaque<'u, 't, U, T, const CU: u8, const CT: u8> + type Opaque<'s, 't, S, T, const CS: u8, const CT: u8> = impl Sized; #[inline(always)] - fn f<'u, 't, U, T, const CU: u8, const CT: u8>( - s: Ty<'u, U, CU>, x: &'t (), y: T, - ) -> Self::Opaque<'u, 't, U, T, CU, CT> { - (s, x, y, CU, CT) + fn f<'s, 't, S, T, const CS: u8, const CT: u8>( + ) -> Self::Opaque<'s, 't, S, T, CS, CT> { + C::<'s, 't, S, T, CS, CT> { _p: PhantomData } + } + } +} +``` + +### Reference desugaring for `use<..>` in RPITIT + +Similarly, we can describe the semantics of `use<..>` in return position `impl Trait` in trait (RPITIT) using anonymous associated types and ATPIT. If we consider the following code: + +```rust +use core::marker::PhantomData; + +struct C< + 'r, 's, 't, R, S, T, const CR: u8, const CS: u8, const CT: u8, +> { + _p: PhantomData<(&'r (), &'s (), &'t (), R, S, T)>, +} + +trait Trait<'r, R, const CR: u8> { + fn f<'t, T, const CT: u8>( + ) -> impl use<'r, 't, R, T, CR, CT, Self> Sized; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // This is the `use<..>` specifier in the trait definition. +} + +struct Ty<'s, S, const CS: u8>(&'s (), S); +impl<'r, 's, R, S, const CR: u8, const CS: u8> Trait<'r, R, CR> + for Ty<'s, S, CS> +{ + fn f<'t, T, const CT: u8>( + ) -> impl use<'r, 's, 't, R, S, T, CR, CS, CT> Sized { + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // This is the `use<..>` specifier in the + // trait implementation. + C::<'r, 's, 't, R, S, T, CR, CS, CT> { _p: PhantomData } + } +} +``` + +Then we can desugar this as follows, without the use of `use<..>` specifiers, while preserving equivalent semantics with respect to the capturing of generic parameters: + +```rust +use core::marker::PhantomData; + +struct C< + 'r, 's, 't, R, S, T, const CR: u8, const CS: u8, const CT: u8, +> { + _p: PhantomData<(&'r (), &'s (), &'t (), R, S, T)>, +} + +trait Trait<'r, R, const CR: u8>: Sized { + fn f<'t, T, const CT: u8>( + ) -> >::Opaque<'t, T, CT> + // ^^^^ ^^^^^^^^^ ^^^^^^^^^ + // These are the arguments given to the `use<..>` specifier in the + // trait definition. + // + // Reducing what is captured by removing arguments from that + // `use<..>` is equivalent to removing arguments from here + // and as needed below. + // + // If `Self` is removed from the `use<..>` specifier, that's + // equivalent to replacing `Self` with `()` here and in the impl + // below. I.e., removing `Self` means that we can't capture any + // generic parameters in the impl that are not used as input + // arguments to the trait. + // + // Similarly, if trait input parameters are removed from the + // `use<..>` specifier, they must be removed from the input + // parameters to the trait `G` below. + where Self: _0::G<'r, R, CR>; +} + +struct Ty<'s, S, const CS: u8>(&'s (), S); +impl<'r, 's, R, S, const CR: u8, const CS: u8> Trait<'r, R, CR> + for Ty<'s, S, CS> +{ + fn f<'t, T, const CT: u8>( + ) -> >::Opaque<'t, T, CT> { + <() as _0::H>::f::<'r, 's, 't, R, S, T, CR, CS, CT>() + } +} + +mod _0 { + use super::*; + pub trait G<'r, R, const CR: u8> { + type Opaque<'t, T, const CT: u8>; + } + impl<'r, 's, R, S, const CR: u8, const CS: u8> G<'r, R, CR> + for Ty<'s, S, CS> + { + type Opaque<'t, T, const CT: u8> + = <() as H>::Opaque<'r, 's, 't, R, S, T, CR, CS, CT>; + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // These are the arguments given to the `use<..>` specifier in + // the trait implementation. + // + // Reducing what is captured by removing arguments from that + // `use<..>` is equivalent to removing arguments from this + // list and as needed elsewhere. + } + pub trait H { + type Opaque< + 'r, 's, 't, R, S, T, + const CR: u8, const CS: u8, const CT: u8, + >; + fn f< + 'r, 's, 't, R, S, T, + const CR: u8, const CS: u8, const CT: u8, + >() -> Self::Opaque<'r, 's, 't, R, S, T, CR, CS, CT>; + } + impl H for () { + type Opaque< + 'r, 's, 't, R, S, T, + const CR: u8, const CS: u8, const CT: u8, + > = impl Sized; + #[inline(always)] + fn f< + 'r, 's, 't, R, S, T, + const CR: u8, const CS: u8, const CT: u8, + >() -> Self::Opaque<'r, 's, 't, R, S, T, CR, CS, CT> { + C::<'r, 's, 't, R, S, T, CR, CS, CT> { _p: PhantomData } } } } From f06ae6ad48062f0866e3b13fa31a8131ca18e8e9 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 29 Apr 2024 23:36:00 +0000 Subject: [PATCH 11/17] Simplify RPITIT desugaring We had added an RPITIT desugaring that was rather complicated. This complication was due to trying to explain what it would mean to not capture generic parameters that are part of the trait header. However, it's likely that there's no good way to express that semantic in the surface syntax. Let's instead simplify the desugaring and make a note of its limitations. --- text/3617-precise-capturing.md | 117 ++++----------------------------- 1 file changed, 11 insertions(+), 106 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 2595949435d..2008cbc8850 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -207,7 +207,7 @@ The [syntax for `impl Trait`][] is revised and extended as follows: ## Reference desugarings -The desugarings that follow can be used to answer questions about how `use<..>` is expected to work with respect to the capturing of generic parameters in opaque types. +The desugarings that follow can be used to answer questions about how `use<..>` is expected to work with respect to the capturing of generic parameters. ### Reference desugaring for `use<..>` in RPIT @@ -275,126 +275,31 @@ mod _0 { ### Reference desugaring for `use<..>` in RPITIT -Similarly, we can describe the semantics of `use<..>` in return position `impl Trait` in trait (RPITIT) using anonymous associated types and ATPIT. If we consider the following code: +Similarly, we can describe the semantics of `use<..>` in return position `impl Trait` in trait (RPITIT) using anonymous associated types. If we consider the following code: ```rust -use core::marker::PhantomData; - -struct C< - 'r, 's, 't, R, S, T, const CR: u8, const CS: u8, const CT: u8, -> { - _p: PhantomData<(&'r (), &'s (), &'t (), R, S, T)>, -} - trait Trait<'r, R, const CR: u8> { fn f<'t, T, const CT: u8>( ) -> impl use<'r, 't, R, T, CR, CT, Self> Sized; // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // This is the `use<..>` specifier in the trait definition. -} - -struct Ty<'s, S, const CS: u8>(&'s (), S); -impl<'r, 's, R, S, const CR: u8, const CS: u8> Trait<'r, R, CR> - for Ty<'s, S, CS> -{ - fn f<'t, T, const CT: u8>( - ) -> impl use<'r, 's, 't, R, S, T, CR, CS, CT> Sized { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // This is the `use<..>` specifier in the - // trait implementation. - C::<'r, 's, 't, R, S, T, CR, CS, CT> { _p: PhantomData } - } + // This is the `use<..>` specifier to desugar. } ``` -Then we can desugar this as follows, without the use of `use<..>` specifiers, while preserving equivalent semantics with respect to the capturing of generic parameters: +Then we can desugar this as follows, without the use of a `use<..>` specifier, while preserving equivalent semantics with respect to the capturing of generic parameters: ```rust -use core::marker::PhantomData; - -struct C< - 'r, 's, 't, R, S, T, const CR: u8, const CS: u8, const CT: u8, -> { - _p: PhantomData<(&'r (), &'s (), &'t (), R, S, T)>, -} - -trait Trait<'r, R, const CR: u8>: Sized { - fn f<'t, T, const CT: u8>( - ) -> >::Opaque<'t, T, CT> - // ^^^^ ^^^^^^^^^ ^^^^^^^^^ - // These are the arguments given to the `use<..>` specifier in the - // trait definition. - // - // Reducing what is captured by removing arguments from that - // `use<..>` is equivalent to removing arguments from here - // and as needed below. - // - // If `Self` is removed from the `use<..>` specifier, that's - // equivalent to replacing `Self` with `()` here and in the impl - // below. I.e., removing `Self` means that we can't capture any - // generic parameters in the impl that are not used as input - // arguments to the trait. - // - // Similarly, if trait input parameters are removed from the - // `use<..>` specifier, they must be removed from the input - // parameters to the trait `G` below. - where Self: _0::G<'r, R, CR>; -} - -struct Ty<'s, S, const CS: u8>(&'s (), S); -impl<'r, 's, R, S, const CR: u8, const CS: u8> Trait<'r, R, CR> - for Ty<'s, S, CS> -{ +trait Trait<'r, R, const CR: u8> { + type _0<'t, T, const CT: u8>: Sized; fn f<'t, T, const CT: u8>( - ) -> >::Opaque<'t, T, CT> { - <() as _0::H>::f::<'r, 's, 't, R, S, T, CR, CS, CT>() - } -} - -mod _0 { - use super::*; - pub trait G<'r, R, const CR: u8> { - type Opaque<'t, T, const CT: u8>; - } - impl<'r, 's, R, S, const CR: u8, const CS: u8> G<'r, R, CR> - for Ty<'s, S, CS> - { - type Opaque<'t, T, const CT: u8> - = <() as H>::Opaque<'r, 's, 't, R, S, T, CR, CS, CT>; - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - // These are the arguments given to the `use<..>` specifier in - // the trait implementation. - // - // Reducing what is captured by removing arguments from that - // `use<..>` is equivalent to removing arguments from this - // list and as needed elsewhere. - } - pub trait H { - type Opaque< - 'r, 's, 't, R, S, T, - const CR: u8, const CS: u8, const CT: u8, - >; - fn f< - 'r, 's, 't, R, S, T, - const CR: u8, const CS: u8, const CT: u8, - >() -> Self::Opaque<'r, 's, 't, R, S, T, CR, CS, CT>; - } - impl H for () { - type Opaque< - 'r, 's, 't, R, S, T, - const CR: u8, const CS: u8, const CT: u8, - > = impl Sized; - #[inline(always)] - fn f< - 'r, 's, 't, R, S, T, - const CR: u8, const CS: u8, const CT: u8, - >() -> Self::Opaque<'r, 's, 't, R, S, T, CR, CS, CT> { - C::<'r, 's, 't, R, S, T, CR, CS, CT> { _p: PhantomData } - } - } + ) -> >::_0<'t, T, CT>; + // ^^^^ ^^^^^^^^^ ^^^^^^^^^ + // These are the arguments given to the `use<..>` specifier. } ``` +Note that this desugaring does not allow for removing from the `use<..>` specifier `Self` or any generics that are input parameters to the trait. This is, in fact, an implementation restriction that is likely to be part of initial rounds of stabilization. + ## Avoiding capture of higher ranked lifetimes in nested opaques According to the Lifetime Capture Rules 2024, a nested `impl Trait` opaque type *must* capture all generic parameters in scope, including higher ranked ones. However, for implementation reasons, Rust does not yet support higher ranked lifetime bounds on nested opaque types (see [#104288][]). Therefore, in Rust 2024, this code, which is valid in Rust 2021, fails to compile: From de7a342f5185c18279e9a83558b5a0c753d657bb Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 5 May 2024 00:52:05 +0000 Subject: [PATCH 12/17] Add clarifications on refinement, reparameterization 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.) --- text/3617-precise-capturing.md | 189 ++++++++++++++++++++++++++++++++- 1 file changed, 185 insertions(+), 4 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 2008cbc8850..b6b9898c00f 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -377,7 +377,7 @@ impl Trait for B { } ``` -If we only know that the value is of some type that implements the trait, then we must assume that the type returned by `foo` *might* have used the lifetime: +If we only know that the value is of some type that implements the trait, then we must assume that the type returned by `foo` *might* use the lifetime: ```rust fn test_trait(x: T) -> impl Sized + 'static { @@ -386,7 +386,7 @@ fn test_trait(x: T) -> impl Sized + 'static { } ``` -However, if we know we have a value of type `B`, we can *rely* on the fact that the lifetime was not used: +However, if we know we have a value of type `B`, we can *rely* on the fact that the lifetime is not used: ```rust fn test_b(x: B) -> impl Sized + 'static { @@ -415,11 +415,15 @@ impl Trait for () { } ``` -Similarly, for consistency, we'll lint against RPITIT cases where less is captured by RPIT in the impl as compared with the trait definition when using `use<..>`. E.g.: +Similarly, for consistency, we'll lint against RPITIT cases where less is captured by RPIT in the impl as compared with the trait definition when using `use<..>`. + +### Examples of refinement + +In keeping with the rule above, we consider it refining if we don't capture in the impl all of the generic parameters from the function signature that are captured in the trait definition: ```rust trait Trait { - fn foo(&self) -> impl Sized; + fn foo(&self) -> impl Sized; // Or: `impl use<'_, Self> Sized` } impl Trait for () { @@ -434,6 +438,183 @@ impl Trait for () { } ``` +Similarly, if we don't capture, in the impl, any generic parameter applied as an argument to the trait in the impl header when the corresponding generic parameter is captured in the trait definition, that is refining. E.g.: + +```rust +trait Trait<'x> { + fn f() -> impl Sized; // Or: `impl use<'x, Self> Sized` +} + +impl<'a> Trait<'a> for () { + fn f() -> impl use<> Sized {} +//~^ WARN impl trait in impl method signature does not match +//~| trait method signature +//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended +//~| for this to be part of the public API of this crate +//~| NOTE we are soliciting feedback, see issue #121718 +//~| +//~| for more information +} +``` + +This remains true even if the trait impl is *reparameterized*. In that case, it is refining unless *all* generic parameters applied in the impl header as generic arguments for the corresponding trait parameter are captured in the impl when that parameter is captured in the trait definition, e.g.: + +```rust +trait Trait { + fn f() -> impl Sized; // Or: `impl use Sized` +} + +impl<'a, 'b> Trait<(&'a (), &'b ())> for () { + fn f() -> impl use<'b> Sized {} +//~^ WARN impl trait in impl method signature does not match +//~| trait method signature +//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended +//~| for this to be part of the public API of this crate +//~| NOTE we are soliciting feedback, see issue #121718 +//~| +//~| for more information +} +``` + +Similarly, it's refining if `Self` is captured in the trait definition and, in the impl, we don't capture all of the generic parameters that are applied in the impl header as generic arguments to the `Self` type, e.g.: + +```rust +trait Trait { + fn f() -> impl Sized; // Or: `impl use Sized` +} + +struct S(T); +impl<'a, 'b> Trait for S<(&'a (), &'b ())> { + fn f() -> impl use<'b> Sized {} +//~^ WARN impl trait in impl method signature does not match +//~| trait method signature +//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended +//~| for this to be part of the public API of this crate +//~| NOTE we are soliciting feedback, see issue #121718 +//~| +//~| for more information +} +``` + +## Lifetime equality + +While the capturing of generic parameters is generally syntactic, this is currently allowed in Rust 2021: + +```rust +//@ edition: 2021 +fn foo<'a: 'b, 'b: 'a>() -> impl Sized + 'b { + core::marker::PhantomData::<&'a ()> +} +``` + +Rust 2021 does not adhere to the Lifetime Capture Rules 2024 for bare RPITs such as this. Correspondingly, lifetimes are only captured when they appear in the bounds. Here, `'b` but not `'a` appears in the bounds, yet we're still able to capture `'a` due to the fact that it must be equal to `'b`. + +To preserve consistency with this, the following is also valid: + + +```rust +fn foo<'a: 'b, 'b: 'a>() -> impl use<'b> Sized { + core::marker::PhantomData::<&'a ()> +} +``` + +A more difficult case is where, in the trait definition, only a subset of the generic parameters on the trait are captured, and in the impl we capture a lifetime *not* applied syntactically as an argument for one of those captured parameters but which is equal to a lifetime that is applied as an argument for one of the captured parameters, e.g.: + +```rust +trait Trait<'x, 'y> { + fn f() -> impl use<'y, Self> Sized; +} + +impl<'a: 'b, 'b: 'a> Trait<'a, 'b> for () { + fn f() -> impl use<'b> Sized { + core::marker::PhantomData::<&'a ()> + } +} +``` + +For the purposes of this RFC, in the interest of consistency with the above cases, we're going to say that this is valid. However, as mentioned elsewhere, partial capturing of generics that are input parameters to the trait (including `Self`) is unlikely to be part of initial rounds of stabilization, and it's possible that implementation experience may lead us to a different answer for this case. + +## Reparameterization + +In Rust, trait impls may be parameterized over a different set of generics than the trait itself. E.g.: + +```rust +trait Trait { + fn f() -> impl use Sized; +} + +impl<'a, B, const C: usize> Trait<(), (&'a (), B, [(); C])> for () { + fn f() -> impl use<'a, B, C> Sized { + core::marker::PhantomData::<(&'a (), B, [(); C])> + } +} +``` + +In these cases, what we look at is how these generics are applied as arguments to the trait in the impl header. In this example, all of `'a`, `B`, and `C` are applied in place of the `Y` input parameter to the trait. Since `Y` is captured in the trait definition, we're correspondingly allowed to capture `'a`, `B`, and `C` in the impl. + +## The `Self` type + +In trait definitions (but not elsewhere), `use<..>` may capture `Self`. Doing so means that in the impl, the opaque type may capture any generic parameters that are applied as generic arguments to the `Self` type. E.g.: + +```rust +trait Trait { + fn f() -> impl use Sized; +} + +struct S(T); +impl<'a, B, const C: usize> Trait for S<(&'a (), B, [(); C])> { + fn f() -> impl use<'a, B, C> Sized { + core::marker::PhantomData::<(&'a (), B, [(); C])> + } +} +``` + +## Handling of projection types + +If we apply, in a trait impl header, a projection type to a trait in place of a parameter that is captured in the trait definition, that does not allow us to capture in the impl the generic parameter from which the type is projected. E.g.: + +```rust +trait Trait { + fn f() -> impl use Sized; +} + +impl Trait for () { + fn f() -> impl use Sized {} + //~^ ERROR cannot capture `A` +} +``` + +The reason this is an error is related to the fact that, in Rust, a generic parameter used as an associated type does not constrain that generic parameter in the impl. E.g.: + +```rust +trait Trait { + type Ty; +} + +impl Trait for () { +//~^ ERROR the type parameter `A` is not constrained + type Ty = A; +} +``` + +## Meaning of capturing a const generic parameter + +As with other generic parameters, a const generic parameter must be captured in the opaque type for it to be used in the hidden *type*. E.g., we must capture `C` here: + +```rust +fn f() -> impl use Sized { + [(); C] +} +``` + +However, note that we do not need to capture `C` just to use it as a *value*, e.g.: + +```rust +fn f() -> impl use<> Sized { + C + 1 +} +``` + ## 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.: From 3ede8f137d16a06e28fb6770457d73657b44e8d3 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 31 May 2024 07:05:57 +0000 Subject: [PATCH 13/17] Leave the syntax as a partially unresolved question 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. --- text/3617-precise-capturing.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index b6b9898c00f..9f2cc4572da 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -750,7 +750,7 @@ We considered a number of different possible syntaxes before landing on `impl us ### `impl use<..> Trait` -This is the syntax chosen in this RFC. +This is the syntax used throughout this RFC (but see the [unresolved questions][]). Using a separate keyword makes this syntax more scalable in the sense that we can apply `use<..>` in other places. @@ -893,14 +893,14 @@ If these generics are more like the generic *arguments* above (*Intuition #1*), However, if these generics are more like the generic *parameters* above (*Intuition #2*), then `use<..> impl Trait` makes more sense. In Rust, when we're putting generic parameters into scope, they appear before the type. -Since both intuitions are valid, but each argues for a different syntax choice, picking one is tough. The authors are sympathetic to both choices. The key historical and tiebreaker factors leading to our choice of the `impl use<..> Trait` syntax in this RFC are: +Since both intuitions are valid, but each argues for a different syntax choice, picking one is tough. The authors are sympathetic to both choices. The key historical and tiebreaker factors leading to our use of the `impl use<..> Trait` syntax in this RFC are: - The original longstanding and motivating semantic intuition for this feature was *Intuition #1*, and it argues for this syntax. The second intuition, *Intuition #2*, was only developed in the process of writing this RFC and after most of this RFC had been written. - The `use<..> impl Trait` syntax was never proposed before this RFC was written (it may have been inspired by the presentation in this RFC of the second intuition), and in discussion, no clear consensus has yet emerged in its favor. - There are some practical costs that exist for `use<..> impl Trait` that don't for `impl use<..> Trait`. - The "obvious" syntax for this feature is `impl<..> Trait`. We may yet someday want to switch to this, and migrating from `impl use<..> Trait` seems like a smaller step. -We are *not* leaving this as an open question, because given that there have already been substantial and productive discussions on this topic, and given that it's a bit of a coin flip where we're likely to be happy at the end of the day with either choice, it seems better to just pick one. But all questions are in some sense open until stabilization, if feelings shift far enough and an alternate consensus emerges, and the authors hope that people will take the opportunity to experiment with and experience the syntax on nightly. +Nonetheless, we leave this as an [unresolved question][]. ### `impl Trait & ..` @@ -940,6 +940,21 @@ We could use a new short keyword such as `via`. This has the number 1 and 2 dra We could say `use('t, T)` or `use['t, T]`. However, in Rust today, generic parameters always fall within angle brackets, even when being applied to a type. Doing something different here could feel inconsistent and doesn't seem warranted. +# Unresolved questions +[unresolved question]: #unresolved-questions +[unresolved questions]: #unresolved-questions + +## Syntax question + +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. + # Future possibilities [future-possibilities]: #future-possibilities From ee785c81b26eed121c465db1ea8acd2605c6fb2d Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 5 Jun 2024 05:57:10 +0000 Subject: [PATCH 14/17] Clarify where the older lifetime capture rules apply 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. --- text/3617-precise-capturing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index 9f2cc4572da..da1cbfa1b2b 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -38,7 +38,7 @@ Overcapturing limits how callers can use returned opaque types in ways that are ## Lifetime Capture Rules 2024 -In Rust 2021 and earlier editions, all type parameters in scope are implicitly captured in RPIT-like `impl Trait` opaque types. In these editions, lifetime parameters are not implicitly captured unless named in the bounds of the opaque. This resulted, among other things, in the use of "the `Captures` trick". See [RFC 3498][] for more details about this. +All type parameters in scope are implicitly captured in RPIT-like `impl Trait` opaque types. In Rust 2021 and earlier editions, for RPIT on bare functions and on inherent functions and methods, lifetime parameters are not implicitly captured unless named in the bounds of the opaque. This resulted, among other things, in the use of "the `Captures` trick". See [RFC 3498][] for more details about this. In RFC 3498, we decided to capture all in-scope generic parameters in RPIT-like `impl Trait` opaque types, across all editions, for new features we were stabilizing such as return position `impl Trait` in Trait (RPITIT) and associated type position `impl Trait` (ATPIT), and to capture all in-scope generic parameters for RPIT on bare functions and on inherent functions and methods starting in the Rust 2024 edition. Doing this made the language more predictable and consistent, eliminated weird "tricks", and, by solving key problems, allowed for the stabilization of RPITIT. From 8f641ec3c0b8d26b2622a89c74f7ab62349ecec1 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 5 Jun 2024 06:02:31 +0000 Subject: [PATCH 15/17] Clarify that `use<..>` appears before any bounds 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. --- text/3617-precise-capturing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index da1cbfa1b2b..a85623364d5 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -62,7 +62,7 @@ We need some way to migrate this kind of code. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -In all editions, RPIT-like `impl Trait` opaque types may include `use<..>` in the bound to specify which in-scope generic parameters are captured or that no in-scope generic parameters are captured (with `use<>`). If `use<..>` is provided, it entirely overrides the implicit rules for which generic parameters are captured. +In all editions, RPIT-like `impl Trait` opaque types may include `use<..>` before any bounds to specify which in-scope generic parameters are captured or that no in-scope generic parameters are captured (with `use<>`). If `use<..>` is provided, it entirely overrides the implicit rules for which generic parameters are captured. 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. From dc258ba2a2d09d5060547b02ae1cb7c335e935f0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 5 Jun 2024 06:04:10 +0000 Subject: [PATCH 16/17] Be less decisive in a statement about the syntax 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. --- text/3617-precise-capturing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index a85623364d5..dece06cb463 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -857,7 +857,7 @@ In the above example and throughout Rust, we observe the following order: *type* This observation, that we're applying generic *arguments* to the opaque type and that the `impl` keyword is the stand-in for that type, is also a strong argument in favor of `impl<..> Trait` syntax. It's conceivable that we'll later, with more experience and consistently with [Stroustrup's Rule][], decide that we want to be more concise and adopt the `impl<..> Trait` syntax after all. One of the advantages of placing `use<..>` after `impl` is that there would be less visual and conceptual churn in later making that change. -Finally, there's one other practical advantage to placing `impl` before `use<..>`. If we were to do it the other way and place `use<..>` before `impl`, we would need to make a backward incompatible change to the `ty` macro matcher fragment specifier. This would require us to migrate this specifier according to our policy in [RFC 3531][]. This is something we could do, but it is a cost on us and on our users, even if only a modest one, and combined with the other good reasons that argue for `impl use<..> Trait` (or even `impl<..> Trait`), it doesn't seem a cost that's worth paying. +Finally, there's one other practical advantage to placing `impl` before `use<..>`. If we were to do it the other way and place `use<..>` before `impl`, we would need to make a backward incompatible change to the `ty` macro matcher fragment specifier. This would require us to migrate this specifier according to our policy in [RFC 3531][]. This is something we could do, but it is a cost on us and on our users, even if only a modest one. [RFC 3531]: https://github.com/rust-lang/rfcs/blob/master/text/3531-macro-fragment-policy.md [Stroustrup's Rule]: https://www.thefeedbackloop.xyz/stroustrups-rule-and-layering-over-time/ From 11dc4e43c1b2c8c4969d209c1743b0d6209b015f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Wed, 5 Jun 2024 06:06:52 +0000 Subject: [PATCH 17/17] Do some copyediting 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. --- text/3617-precise-capturing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/3617-precise-capturing.md b/text/3617-precise-capturing.md index dece06cb463..4979b70b7a3 100644 --- a/text/3617-precise-capturing.md +++ b/text/3617-precise-capturing.md @@ -42,7 +42,7 @@ All type parameters in scope are implicitly captured in RPIT-like `impl Trait` o In RFC 3498, we decided to capture all in-scope generic parameters in RPIT-like `impl Trait` opaque types, across all editions, for new features we were stabilizing such as return position `impl Trait` in Trait (RPITIT) and associated type position `impl Trait` (ATPIT), and to capture all in-scope generic parameters for RPIT on bare functions and on inherent functions and methods starting in the Rust 2024 edition. Doing this made the language more predictable and consistent, eliminated weird "tricks", and, by solving key problems, allowed for the stabilization of RPITIT. -However, the expansion of the RPIT rules in Rust 2024 means that some existing uses of RPIT, when migrated to Rust 2024, will now capture lifetime parameters that were not previously captured, and this may result in code failing to compile. For example, consider: +However, the expansion of the RPIT capture rules in Rust 2024 means that some existing uses of RPIT, when migrated to Rust 2024, will now capture lifetime parameters that were not previously captured, and this may result in code failing to compile. For example, consider: ```rust //@ edition: 2021 @@ -64,7 +64,7 @@ We need some way to migrate this kind of code. In all editions, RPIT-like `impl Trait` opaque types may include `use<..>` before any bounds to specify which in-scope generic parameters are captured or that no in-scope generic parameters are captured (with `use<>`). If `use<..>` is provided, it entirely overrides the implicit rules for which generic parameters are captured. -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. +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 example, we can solve the overcapturing in the original motivating example by writing: @@ -703,7 +703,7 @@ As we saw in the reference desugaring above, associated type position `impl Trai As it turned out, there are four problems with this: -1. ATPIT/TAIT is too indirect a solution. +1. These features are too indirect a solution. 2. They might not be stabilized in time. 3. They would lead to a worse migration story. 4. We would want this syntax anyway. @@ -754,7 +754,7 @@ This is the syntax used throughout this RFC (but see the [unresolved questions][ Using a separate keyword makes this syntax more scalable in the sense that we can apply `use<..>` in other places. -Conveniently, the word "use" is quite appropriate here, since we are *using* the generic parameters in the type of the opaque type and allowing the generic parameters to be *used* in the hidden type. That is, with `use`, we are bringing the generic parameters *into scope* for the hidden type, and `use` is the keyword in Rust for bringing things into scope. +Conveniently, the word "use" is quite appropriate here, since we are *using* the generic parameters in the opaque type and allowing the generic parameters to be *used* in the hidden type. That is, with `use`, we are bringing the generic parameters *into scope* for the hidden type, and `use` is the keyword in Rust for bringing things into scope. 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.