|
1 |
| -# RPIT lifetime capture |
| 1 | +# RPIT lifetime capture rules |
2 | 2 |
|
3 | 3 | 🚧 The 2024 Edition has not yet been released and hence this section is still "under construction".
|
4 | 4 |
|
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 |
7 | 9 |
|
8 | 10 | ## Summary
|
9 | 11 |
|
| 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 | + |
10 | 17 | ## Details
|
11 | 18 |
|
| 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 | + |
12 | 160 | ## 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