You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In the design meeting for the 2024 lifetime capture rules, we
presented TAIT as being the intended solution to the problem of
overcapturing. By "intended", we simply meant that it is known to be
a full solution, and since it has accepted RFCs and a consensus to
stabilize it in some form, it's more *intended* than other solutions
lacking those things.
However, as applied to the rules in this RFC, the only thing that's
important is that *some* solution is stabilized that allows for
existing code to continue to be expressed in some manner. The RFC had
already said this (e.g., "...is contingent on the stabilization of
TAIT or some other solution..."), but it was a bit obscured by the
emphasis on TAIT.
Let's deemphasize TAIT and describe the landscape of solutions more
fully so as to keep the focus on the new capture rules. We'll move
the discussion of TAIT out of the normative section entirely and purge
the word "intended" from the document.
(Thanks to @tmandry for pointing this out.)
In Rust 2021, lifetimes within type parameters are automatically captured in RPIT opaque types, and both lifetime parameters and lifetimes within type parameters are automatically captured in `async fn` opaque types. Additionally capturing lifetime parameters in RPIT opaque types may make this problem somewhat worse.
212
212
213
-
The intended solution for this problem is type alias `impl Trait` (TAIT). The stabilization of the 2024 lifetime capture rules in this RFC is contingent on the stabilization of TAIT or some other solution for precise capturing that will allow all code that is allowed under Rust 2021 to be expressed, in some cases with syntactic changes, in Rust 2024.
213
+
There are a number of possible solutions to this problem. One appealing partial solution is to more fully implement the rules specified in [RFC 1214][]. This would allow type and lifetime parameters that do not outlive a specified bound to mostly act as if they were not captured. See [Appendix G][] for a full discussion of this.
214
214
215
-
For an example of how TAIT is intended to be used to fix overcapturing, see the section titled "[TAIT as the solution to overcapturing]".
215
+
Another solution would be to add syntax for precisely specifying which type and lifetime parameters to capture. One proposal for this syntax is described in [Appendix F][].
216
+
217
+
Type alias `impl Trait` (TAIT) is another solution. It has accepted RFCs (see [RFC 2515][], [RFC 2071][]), it's implemented and actively maintained in nightly Rust, and there is a consensus to stabilize it in some form. The stabilization of TAIT would allow all currently accepted code to continue to be expressed with precisely the same semantics. See [Appendix I][] for further details on how TAIT can be used to precisely control the capturing of type and lifetime parameters.
218
+
219
+
The stabilization of the 2024 lifetime capture rules in this RFC is contingent on the stabilization of some solution for precise capturing that will allow all code that is allowed under Rust 2021 to be expressed, in some cases with syntactic changes, in Rust 2024.
[TAIT as the solution to overcapturing]: #tait-as-the-solution-to-overcapturing
312
-
313
-
As we described above, sometimes the capture rules result in unwanted type and lifetime parameters being captured. This happens in Rust 2021 due to the RPIT rules for capturing lifetimes from all in-scope type parameters and the `async fn` rules for capturing all in-scope type and lifetime parameters. Under this RFC, in Rust 2024, lifetime parameters could also be overcaptured by RPIT.
314
-
315
-
The intended solution to this is type alias `impl Trait` (TAIT). It works as follows. Consider this overcaptures scenario in Rust 2024:
316
-
317
-
```rust
318
-
fnfoo<'a, T>(_:&'a (), _:T) ->implSized { () }
319
-
// ^^^^^^^^^^
320
-
// The returned opaque type captures `'a` and `T`
321
-
// but the hidden type does not use either.
322
-
323
-
fnbar<'a, 'b>(x:&'a (), y:&'b ()) {
324
-
fnis_static<T: 'static>(_:T) {}
325
-
is_static(foo(x, y));
326
-
// ^^^^^^^^^
327
-
// Error: `foo` captures `'a` and `'b`.
328
-
}
329
-
```
330
-
331
-
In the above code, we want to rely on the fact that `foo` does not actually use any lifetimes in the returned hidden type. We can't do that using RPIT because there's no way to prevent the opaque type from capturing too much. However, we can use TAIT to solve this problem elegantly as follows:
332
-
333
-
```rust
334
-
#![feature(type_alias_impl_trait)]
335
-
336
-
typeFooRet=implSized;
337
-
fnfoo<'a, T>(_:&'a (), _:T) ->FooRet { () }
338
-
// ^^^^^^
339
-
// The returned opaque type does NOT capture `'a` or `T`.
340
-
341
-
fnbar<'a, 'b>(x:&'a (), y:&'b ()) {
342
-
fnis_static<T: 'static>(_:T) {}
343
-
is_static(foo(x, y)); // OK.
344
-
}
345
-
```
315
+
## Overcapturing
346
316
347
-
The type alias `FooRet` has no generic parameters, so none are capturedin the opaque type. It's always possible to desugar an RPIT opaque type into a TAIT opaque type that expresses precisely which generic parameters to capture.
317
+
Sometimes the capture rules result in unwanted type and lifetime parameters being captured. This happens in Rust 2021 due to the RPIT rules for capturing lifetimes from all in-scope type parameters and the `async fn` rules for capturing all in-scope type and lifetime parameters. Under this RFC, in Rust 2024, lifetime parameters could also be overcaptured by RPIT.
348
318
349
-
The stabilization of the 2024 lifetime capture rules in this RFC is contingent on the stabilization of TAIT or some other solution for precise capturing that will allow all code that is allowed under Rust 2021 to be expressed, in some cases with syntactic changes, in Rust 2024.
319
+
The stabilization of the 2024 lifetime capture rules in this RFC is contingent on the stabilization of some solution for precise capturing that will allow all code that is allowed under Rust 2021 to be expressed, in some cases with syntactic changes, in Rust 2024.
Under this RFC, TAIT is the intended solution to avoid capturing type and lifetime parameters that should not be captured. If this comes up too often in the future, we may want to consider adding new syntax to `impl Trait` to allow for precise capturing. One proposal for that would look like this:
If other solutions for precise capturing of type and lifetime parameters turn out to be unergonomic or needed too often, we may want to consider adding new syntax to `impl Trait` to allow for precise capturing. One proposal for that would look like this:
Under this RFC, TAIT is the intended solution to avoid capturing type and lifetime parameters that should not be captured. However, when an outlives bound is stated for the opaque type, we can use that bound to allow code to compile that does not currently. Consider:
Future work may relax this current limitation of the compiler. The result of such an improvement would be that, when an outlives bound is specified for the opaque type, any type or lifetime parameters that the compiler could prove to not outlive that bound would act as if they were not captured by the opaque type.
651
+
Future work may relax this current limitation of the compiler by more fully implementing the rules of [RFC 1214][] (see, e.g., [#116733][]). Fixing this completely is believed to require support in the compiler for existential lifetimes (see [#60670][]).
652
+
653
+
The end result of these improvements would be that, when an outlives bound is specified for the opaque type, any type or lifetime parameters that the compiler could prove to not outlive that bound would mostly act as if it were not captured by the opaque type.
654
+
655
+
This would not be quite the same as those type and lifetime parameters not actually being captured. By checking type equality between opaque types where different captured type or lifetime parameters have been substituted, one could tell the difference.
656
+
657
+
Still, this improvement would allow for solving many cases of overcapturing elegantly. Consider this transformation:
Using this transformation (which is described more fully in [Appendix H][]), we can add a specified outlives bound to an RPIT opaque type without changing the effective proof requirements on either the caller or the callee. We can then drop the `Pi: 'o` outlives bound from any type or lifetime parameter that we would like to act as if it were not captured.
666
+
667
+
This comes at the cost of adding an extra early-bound lifetime parameter in the general case. Adding that lifetime parameter may require changing the externally visible API of the function. However, for the common case of adding a `+ 'static` bound, or for any other case where an existing lifetime parameter suffices to specify the needed bounds, this is not a problem.
# Appendix H: Examples of outlives rules on opaque types
680
673
@@ -773,4 +766,54 @@ In the first example, to prove that the opaque type outlives `'short`, the *call
773
766
774
767
(Obviously, the caller then still needs to prove the outlives relationships necessary to satisfy the other specified bounds in the signature of `callee`.)
775
768
776
-
That is, at the cost of an extra early-bound lifetime parameter in the signature of the callee, we can always express an RPIT without a specified outlives bound as an RPIT with a specified outlives bound in a way that does not change the requirements on the caller or the callee. We do this by transforming a signature of the form `fn callee<P1, .., Pn>(..) -> impl Trait` to a signature of the form `fn callee<'o, P1: 'o, .., Pn: 'o>(..) -> impl Trait + 'o`.
769
+
That is, at the cost of an extra early-bound lifetime parameter in the signature of the callee, we can always express an RPIT without a specified outlives bound as an RPIT with a specified outlives bound in a way that does not change the requirements on the caller or the callee. We do this by applying the following transformation:
Sometimes the capture rules result in unwanted type and lifetime parameters being captured. This happens in Rust 2021 due to the RPIT rules for capturing lifetimes from all in-scope type parameters and the `async fn` rules for capturing all in-scope type and lifetime parameters. Under this RFC, in Rust 2024, lifetime parameters could also be overcaptured by RPIT.
784
+
785
+
Type alias `impl Trait` (TAIT) provides a precise solution. It works as follows. Consider this overcaptures scenario in Rust 2024:
786
+
787
+
```rust
788
+
fnfoo<'a, T>(_:&'a (), _:T) ->implSized { () }
789
+
// ^^^^^^^^^^
790
+
// The returned opaque type captures `'a` and `T`
791
+
// but the hidden type does not use either.
792
+
793
+
fnbar<'a, 'b>(x:&'a (), y:&'b ()) {
794
+
fnis_static<T: 'static>(_:T) {}
795
+
is_static(foo(x, y));
796
+
// ^^^^^^^^^
797
+
// Error: `foo` captures `'a` and `'b`.
798
+
}
799
+
```
800
+
801
+
In the above code, we want to rely on the fact that `foo` does not actually use any lifetimes in the returned hidden type. We can't do that using RPIT because there's no way to prevent the opaque type from capturing too much. However, we can use TAIT to solve this problem elegantly as follows:
802
+
803
+
```rust
804
+
#![feature(type_alias_impl_trait)]
805
+
806
+
typeFooRet=implSized;
807
+
fnfoo<'a, T>(_:&'a (), _:T) ->FooRet { () }
808
+
// ^^^^^^
809
+
// The returned opaque type does NOT capture `'a` or `T`.
810
+
811
+
fnbar<'a, 'b>(x:&'a (), y:&'b ()) {
812
+
fnis_static<T: 'static>(_:T) {}
813
+
is_static(foo(x, y)); // OK.
814
+
}
815
+
```
816
+
817
+
The type alias `FooRet` has no generic parameters, so none are captured in the opaque type. It's always possible to desugar an RPIT opaque type into a TAIT opaque type that expresses precisely which generic parameters to capture.
818
+
819
+
The stabilization of the 2024 lifetime capture rules in this RFC is contingent on the stabilization of some solution for precise capturing that will allow all code that is allowed under Rust 2021 to be expressed, in some cases with syntactic changes, in Rust 2024.
0 commit comments