Skip to content

Commit d7f8c13

Browse files
committed
Add chapter for Lifetime Capture Rules 2024
Let's describe the changes to the opaque type lifetime capture rules in some detail. To do this, we first describe precise capturing, as we'll use that syntax both for describing the meaning of capturing and to describe the migration steps. Arguably, some of what we describe isn't strictly tied to the edition. People could migrate to the `use<..>` syntax in any edition. However, this is all part of the same change, both conceptually and practically, so it's better to just describe this in one place.
1 parent ce1bf80 commit d7f8c13

File tree

2 files changed

+305
-4
lines changed

2 files changed

+305
-4
lines changed

src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
- [Additions to the prelude](rust-2024/prelude.md)
4242
- [Add `IntoIterator` for `Box<[T]>`](rust-2024/intoiterator-box-slice.md)
4343
- [`unsafe_op_in_unsafe_fn` warning](rust-2024/unsafe-op-in-unsafe-fn.md)
44-
- [RPIT lifetime capture](rust-2024/rpit-lifetime-capture.md)
44+
- [RPIT lifetime capture rules](rust-2024/rpit-lifetime-capture.md)
4545
- [Disallow references to `static mut`](rust-2024/static-mut-reference.md)
4646
- [Cargo: Remove implicit features](rust-2024/cargo-remove-implicit-features.md)
4747
- [Cargo: Table and key name consistency](rust-2024/cargo-table-key-names.md)
Lines changed: 304 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,313 @@
1-
# RPIT lifetime capture
1+
# RPIT lifetime capture rules
22

33
🚧 The 2024 Edition has not yet been released and hence this section is still "under construction".
44

5-
This feature is partially implemented, and not yet ready for testing.
6-
More information may be found in the tracking issue at <https://github.com/rust-lang/rust/issues/117587>.
5+
This chapter describes changes related to the **Lifetime Capture Rules 2024** introduced in [RFC 3498], including opaque type *precise capturing* introduced in [RFC 3617].
6+
7+
[RFC 3498]: https://github.com/rust-lang/rfcs/pull/3498
8+
[RFC 3617]: https://github.com/rust-lang/rfcs/pull/3617
79

810
## Summary
911

12+
- In all editions, `use<..>` bounds allow for capturing only the lifetime parameters needed in RPIT opaque types.
13+
- If a `use<..>` bound is elided (i.e. not present) then the compiler automatically inserts one according to edition-specific rules.
14+
- In Rust 2024, *all* in-scope generic parameters, including lifetime parameters, are included in these automatically inserted `use<..>` bounds.
15+
- Uses of the `Captures` trick (`Captures<..>` bounds) and of the outlives trick (e.g. `'_` bounds) can be replaced by `use<..>` bounds (in all editions) or removed entirely (in Rust 2024).
16+
1017
## Details
1118

19+
### Capturing and `use<..>` syntax
20+
21+
*Capturing* a generic parameter in an RPIT (return-position impl Trait) opaque type allows for that parameter to be used in the corresponding hidden type. In Rust 1.82, we added `use<..>` bounds that allow specifying explicitly which generic parameters to capture. E.g.:
22+
23+
```rust
24+
# #![feature(precise_capturing)]
25+
fn capture<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
26+
// ~~~~~~~~~~~~~~~~~~~~~~~
27+
// This is the RPIT opaque type.
28+
//
29+
// It captures `'a` and `T`.
30+
(x, y)
31+
//~~~~~~
32+
// The hidden type is: `(&'a (), T)`.
33+
//
34+
// This type can use `'a` and `T` because they were captured.
35+
}
36+
```
37+
38+
Currently, `use<..>` bounds must list all generic type and const parameters in scope, but they may exclude in-scope lifetime parameters. E.g.:
39+
40+
```rust
41+
# #![feature(precise_capturing)]
42+
fn capture<'a, T>(_: &'a (), _: T) -> impl Sized + use<T> {
43+
// ~~~~~~~~~~~~~~~~~~~
44+
// The opaque type captures only `T` and not `'a`.
45+
()
46+
}
47+
```
48+
49+
You might want to capture more lifetimes in order to return a hidden type that uses those lifetimes. Conversely, you might want to capture fewer lifetimes in order to allow the opaque type to be used in more places. E.g., this is an error because the lifetime is captured despite the fact that the hidden type does not use the lifetime:
50+
51+
```rust,compile_fail
52+
# #![feature(precise_capturing)]
53+
fn capture<'a>(_: &'a ()) -> impl Sized + use<'a> {}
54+
55+
fn test<'a>(x: &'a ()) -> impl Sized + 'static {
56+
capture(x)
57+
//~^ ERROR lifetime may not live long enough
58+
}
59+
```
60+
61+
Conversely, this is OK since the opaque type does not capture the lifetime:
62+
63+
```rust
64+
# #![feature(precise_capturing)]
65+
fn capture<'a>(_: &'a ()) -> impl Sized + use<> {}
66+
67+
fn test<'a>(x: &'a ()) -> impl Sized + 'static {
68+
capture(x) //~ OK
69+
}
70+
```
71+
72+
The bounds list of an RPIT opaque type may contain at most one `use<..>` bound. If this bound is present, then the listed generic parameters are the only ones captured.
73+
74+
### Edition-specific rules for elided `use<..>` bounds
75+
76+
If the `use<..>` bound is *elided* (i.e., not present), then the compiler automatically inserts one according to edition-specific rules.
77+
78+
In all editions, all in-scope type and const generic parameters are added to this automatically-inserted `use<..>` bound. E.g.:
79+
80+
```rust
81+
# #![feature(precise_capturing)]
82+
fn f_elided<T, const C: usize>() -> impl Sized {}
83+
// ~~~~~~~~~~
84+
// The `use<..>` bound is elided here.
85+
//
86+
// In all editions, the above is equivalent to:
87+
fn f_explicit<T, const C: usize>() -> impl Sized + use<T, C> {}
88+
```
89+
90+
In Rust 2021 and earlier editions, generic lifetime parameters are *not* included in this automatically-inserted `use<..>` bound for RPIT opaque types in the signature of bare functions and associated functions and methods within inherent impls. However, starting in Rust 2024, these in-scope generic lifetime parameters *are* included. E.g.:
91+
92+
```rust
93+
# #![feature(precise_capturing)]
94+
fn f_elided(_: &()) -> impl Sized {}
95+
// In Rust 2021 and earlier, the above is equivalent to:
96+
fn f_2021(_: &()) -> impl Sized + use<> {}
97+
// In Rust 2024 and later, it's equivalent to:
98+
fn f_2024(_: &()) -> impl Sized + use<'_> {}
99+
```
100+
101+
This makes the behavior consistent with RPIT opaque types in the signature of associated functions and methods within trait impls, uses of RPIT within trait definitions (RPITIT), and opaque `Future` types created by `async fn`, all of which capture all in-scope generic lifetime parameters by default in all editions.
102+
103+
### Outer generic parameters
104+
105+
When we say that an RPIT opaque type captures all in-scope generic parameters, note that this includes generic parameters from an outer impl. E.g.:
106+
107+
```rust
108+
# #![feature(precise_capturing)]
109+
struct S<T, const C: usize>((T, [(); C]));
110+
impl<T, const C: usize> S<T, C> {
111+
// ~~~~~~~~~~~~~~~~~
112+
// These generic parameters are in scope.
113+
fn f_elided<U>() -> impl Sized {}
114+
// ~ ~~~~~~~~~~
115+
// ^ This generic is in scope too.
116+
// ^
117+
// |
118+
// The `use<..>` bound is elided here.
119+
//
120+
// In all editions, it's equivalent to:
121+
fn f_explicit<U>() -> impl Sized + use<T, U, C> {}
122+
}
123+
```
124+
125+
### Lifetimes from higher-ranked binders
126+
127+
Similarly, generic lifetime parameters introduced into scope by a higher-ranked `for<..>` binder are considered to be in scope. E.g.:
128+
129+
```rust
130+
# #![feature(precise_capturing)]
131+
trait Tr<'a> { type Ty; }
132+
impl Tr<'_> for () { type Ty = (); }
133+
134+
fn f_elided() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {}
135+
// In Rust 2021 and earlier, the above is equivalent to:
136+
fn f_2021() -> impl for<'a> Tr<'a, Ty = impl Copy + use<>> {}
137+
// In Rust 2024 and later, it's equivalent to:
138+
//fn f_2024() -> impl for<'a> Tr<'a, Ty = impl Copy + use<'a>> {}
139+
// ~~~~~~~~~~~~~~~~~~~~
140+
// However, note that the capturing of higher-ranked lifetimes in
141+
// nested opaque types is not yet supported.
142+
```
143+
144+
### Argument position impl Trait (APIT)
145+
146+
Anonymous (i.e. unnamed) generic parameters created by the use of APIT (argument position impl Trait) are considered to be in scope when inserting an explicit `use<..>` bound for an elided one. E.g.:
147+
148+
```rust
149+
# #![feature(precise_capturing)]
150+
fn f_elided(_: impl Sized) -> impl Sized {}
151+
// ~~~~~~~~~~
152+
// This is called APIT.
153+
//
154+
// The above is *roughly* equivalent to:
155+
fn f_explicit<_0: Sized>(_: _0) -> impl Sized + use<_0> {}
156+
```
157+
158+
Note that the former is not *exactly* equivalent to the latter because, by naming the generic parameter, turbofish syntax can now be used to provide an argument for it. There is no way to explicitly include an anonymous generic parameter in a `use<..>` bound other than by converting it to a named generic parameter.
159+
12160
## Migration
161+
162+
### Migrating while avoiding overcapturing
163+
164+
The `impl_trait_overcaptures` lint flags RPIT opaque types that will capture additional lifetimes in Rust 2024. This lint is part of the `rust-2024-compatibility` lint group which is automatically applied when running `cargo fix --edition`. In most cases, the lint can automatically insert `use<..>` bounds where needed such that no additional lifetimes are captured in Rust 2024.
165+
166+
To migrate your code to be compatible with Rust 2024, run:
167+
168+
```sh
169+
cargo fix --edition
170+
```
171+
172+
For example, this will change:
173+
174+
```rust
175+
fn f<'a>(x: &'a ()) -> impl Sized { *x }
176+
```
177+
178+
...into:
179+
180+
```rust
181+
# #![feature(precise_capturing)]
182+
fn f<'a>(x: &'a ()) -> impl Sized + use<> { *x }
183+
```
184+
185+
### Migrating cases involving APIT
186+
187+
In some cases, the lint cannot make the change automatically because a generic parameter needs to be given a name so that it can appear within a `use<..>` bound. In these cases, the lint will alert you that a change may need to be made manually. E.g., given:
188+
189+
```rust,edition2021
190+
fn f<'a>(x: &'a (), y: impl Sized) -> impl Sized { (*x, y) }
191+
// ^^ ~~~~~~~~~~
192+
// This is a use of APIT.
193+
//
194+
//~^ WARN `impl Sized` will capture more lifetimes than possibly intended in edition 2024
195+
//~| NOTE specifically, this lifetime is in scope but not mentioned in the type's bounds
196+
#
197+
# fn test<'a>(x: &'a (), y: ()) -> impl Sized + 'static {
198+
# f(x, y)
199+
# }
200+
```
201+
202+
The code cannot be converted automatically because of the use of APIT and the fact that the generic type parameter must be named in the `use<..>` bound. To convert this code to Rust 2024 without capturing the lifetime, you must name that type parameter. E.g.:
203+
204+
```rust
205+
# #![feature(precise_capturing)]
206+
# #![deny(impl_trait_overcaptures)]
207+
fn f<'a, T: Sized>(x: &'a (), y: T) -> impl Sized + use<T> { (*x, y) }
208+
// ~~~~~~~~
209+
// The type parameter has been named here.
210+
#
211+
# fn test<'a>(x: &'a (), y: ()) -> impl Sized + use<> {
212+
# f(x, y)
213+
# }
214+
```
215+
216+
Note that this changes the API of the function slightly as a type argument can now be explicitly provided for this parameter using turbofish syntax. If this is undesired, you might consider instead whether you can simply continue to elide the `use<..>` bound and allow the lifetime to be captured. This might be particularly desirable if you might in the future want to use that lifetime in the hidden type and would like to save space for that.
217+
218+
### Migrating away from the `Captures` trick
219+
220+
Prior to the introduction of precise capturing `use<..>` bounds in Rust 1.82, correctly capturing a lifetime in an RPIT opaque type often required using the `Captures` trick. E.g.:
221+
222+
```rust
223+
#[doc(hidden)]
224+
pub trait Captures<T: ?Sized> {}
225+
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}
226+
227+
fn f<'a, T>(x: &'a (), y: T) -> impl Sized + Captures<(&'a (), T)> {
228+
// ~~~~~~~~~~~~~~~~~~~~~
229+
// This is called the `Captures` trick.
230+
(x, y)
231+
}
232+
#
233+
# fn test<'t, 'x>(t: &'t (), x: &'x ()) {
234+
# f(t, x);
235+
# }
236+
```
237+
238+
With the `use<..>` bound syntax, the `Captures` trick is no longer needed and can be replaced with the following in all editions:
239+
240+
```rust
241+
# #![feature(precise_capturing)]
242+
fn f<'a, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
243+
(x, y)
244+
}
245+
#
246+
# fn test<'t, 'x>(t: &'t (), x: &'x ()) {
247+
# f(t, x);
248+
# }
249+
```
250+
251+
In Rust 2024, the `use<..>` bound can often be elided entirely, and the above can be written simply as:
252+
253+
<!-- TODO: edition2024 -->
254+
```rust
255+
# #![feature(lifetime_capture_rules_2024)]
256+
fn f<'a, T>(x: &'a (), y: T) -> impl Sized {
257+
(x, y)
258+
}
259+
#
260+
# fn test<'t, 'x>(t: &'t (), x: &'x ()) {
261+
# f(t, x);
262+
# }
263+
```
264+
265+
There is no automatic migration for this, and the `Captures` trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.
266+
267+
### Migrating away from the outlives trick
268+
269+
Prior to the introduction of precise capturing `use<..>` bounds in Rust 1.82, it was common to use the "outlives trick" when a lifetime needed to be used in the hidden type of some opaque. E.g.:
270+
271+
```rust
272+
fn f<'a, T: 'a>(x: &'a (), y: T) -> impl Sized + 'a {
273+
// ~~~~ ~~~~
274+
// ^ This is the outlives trick.
275+
// |
276+
// This bound is needed only for the trick.
277+
(x, y)
278+
// ~~~~~~
279+
// The hidden type is `(&'a (), T)`.
280+
}
281+
```
282+
283+
This trick was less baroque than the `Captures` trick, but also less correct. As we can see in the example above, even though any lifetime components within `T` are independent from the lifetime `'a`, we're required to add a `T: 'a` bound in order to make the trick work. This created undue and surprising restrictions on callers.
284+
285+
Using precise capturing, you can write the above instead, in all editions, as:
286+
287+
```rust
288+
# #![feature(precise_capturing)]
289+
fn f<T>(x: &(), y: T) -> impl Sized + use<'_, T> {
290+
(x, y)
291+
}
292+
#
293+
# fn test<'t, 'x>(t: &'t (), x: &'x ()) {
294+
# f(t, x);
295+
# }
296+
```
297+
298+
In Rust 2024, the `use<..>` bound can often be elided entirely, and the above can be written simply as:
299+
300+
<!-- TODO: edition2024 -->
301+
```rust
302+
# #![feature(precise_capturing)]
303+
# #![feature(lifetime_capture_rules_2024)]
304+
fn f<T>(x: &(), y: T) -> impl Sized {
305+
(x, y)
306+
}
307+
#
308+
# fn test<'t, 'x>(t: &'t (), x: &'x ()) {
309+
# f(t, x);
310+
# }
311+
```
312+
313+
There is no automatic migration for this, and the outlives trick still works in Rust 2024, but you might want to consider migrating code manually away from using this old trick.

0 commit comments

Comments
 (0)