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
Copy file name to clipboardExpand all lines: posts/2024-09-05-impl-trait-capture-rules.md
+15-15Lines changed: 15 additions & 15 deletions
Original file line number
Diff line number
Diff line change
@@ -35,7 +35,7 @@ The use of `-> impl Iterator` in return position here means that the function re
35
35
36
36
Although callers don't know the exact type, they do need to know that it will continue to borrow the `data` argument so that they can ensure that the `data` reference remains valid while iteration occurs. Further, callers must be able to figure this out based solely on the type signature, without looking at the function body.
37
37
38
-
Rust's current rules are that a return-position `impl Trait` value can only use a reference if the lifetime of that reference appears in the `impl Trait` itself. In this example, `impl Iterator<Item = ProcessedDatum>` does not reference any lifetimes, and therefore capturing `datums` is illegal. You can see this for yourself [on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2448fc4ec9e763c538aaba897433f9b5).
38
+
Rust's current rules are that a return-position `impl Trait` value can only use a reference if the lifetime of that reference appears in the `impl Trait` itself. In this example, `impl Iterator<Item = ProcessedDatum>` does not reference any lifetimes, and therefore capturing `data` is illegal. You can see this for yourself [on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2448fc4ec9e763c538aaba897433f9b5).
39
39
40
40
The error message ("hidden type captures lifetime") you get in this scenario is not the most intuitive, but it does come with a useful suggestion for how to fix it:
41
41
@@ -53,21 +53,21 @@ Following a slightly more explicit version of this advice, the function signatur
53
53
54
54
55
55
```rust
56
-
fnprocess_datums<'d>(
57
-
datums:&'d [Datum]
56
+
fnprocess_data<'d>(
57
+
data:&'d [Datum]
58
58
) ->implIterator<Item=ProcessedDatum> + 'd {
59
-
datums
59
+
data
60
60
.iter()
61
61
.map(|datum|datum.process())
62
62
}
63
63
```
64
64
65
-
In this version, the lifetime `'d` of the datums is explicitly referenced in the `impl Trait` type, and so it is allowed to be used. This is also a signal to the caller that the borrow for `datums` must last as long as the iterator is in use, which means that it (correctly) flags an error in an example like this ([try it on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afd9278ac887c0b2fc08bc868200808f)):
65
+
In this version, the lifetime `'d` of the data is explicitly referenced in the `impl Trait` type, and so it is allowed to be used. This is also a signal to the caller that the borrow for `data` must last as long as the iterator is in use, which means that it (correctly) flags an error in an example like this ([try it on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=afd9278ac887c0b2fc08bc868200808f)):
66
66
67
67
```rust
68
-
letmutdatums:Vec<Datum> =vec![Datum::default()];
69
-
letiter=process_datums(&datums);
70
-
datums.push(Datum::default()); // <-- Error!
68
+
letmutdata:Vec<Datum> =vec![Datum::default()];
69
+
letiter=process_data(&data);
70
+
data.push(Datum::default()); // <-- Error!
71
71
iter.next();
72
72
```
73
73
@@ -94,17 +94,17 @@ Adding a `+ '_` argument to `impl Trait` may be confusing, but it's not terribly
94
94
```rust
95
95
fnprocess<'c, T> {
96
96
context:&'cContext,
97
-
datums:Vec<T>,
97
+
data:Vec<T>,
98
98
) ->implIterator<Item= ()> + 'c {
99
-
datums
99
+
data
100
100
.into_iter()
101
101
.map(|datum|context.process(datum))
102
102
}
103
103
```
104
104
105
-
Here the `process` function applies `context.process` to each of the elements in `datums` (of type `T`). Because the return value uses `context`, it is declared as `+ 'c`. Our real goal here is to allow the return type to use `'c`; writing `+ 'c` achieves that goal because `'c` not appears in the bound listing. However, while writing `+ 'c` is a convenient way to make `'c` appear in the bounds, also means that the hidden type must outlive `'c`. This requirement is not needed and will in fact lead to a compilation error in this example ([try it on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b742fbf9b083a6e837db0b170489f34a)).
105
+
Here the `process` function applies `context.process` to each of the elements in `data` (of type `T`). Because the return value uses `context`, it is declared as `+ 'c`. Our real goal here is to allow the return type to use `'c`; writing `+ 'c` achieves that goal because `'c` not appears in the bound listing. However, while writing `+ 'c` is a convenient way to make `'c` appear in the bounds, also means that the hidden type must outlive `'c`. This requirement is not needed and will in fact lead to a compilation error in this example ([try it on the playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b742fbf9b083a6e837db0b170489f34a)).
106
106
107
-
The reason that this error occurs is a bit subtle. The hidden type is an iterator type based on the result of `datums.into_iter()`, which will include the type `T`. Because of the `+ 'c` bound, the hidden type must outlive `'c`, which in turn means that `T` must outlive `'c`. But `T` is a generic parameter, so the compiler requires a where-clause like `where T: 'c`. This where-clause means "it is safe to create a reference with lifetime `'c` to the type `T`". But in fact we don't create any such reference, so the where-clause should not be needed. It is only needed because used the convenient-but-sometimes-incorrect workaround of adding `+ 'c` to the bounds of our `impl Trait`.
107
+
The reason that this error occurs is a bit subtle. The hidden type is an iterator type based on the result of `data.into_iter()`, which will include the type `T`. Because of the `+ 'c` bound, the hidden type must outlive `'c`, which in turn means that `T` must outlive `'c`. But `T` is a generic parameter, so the compiler requires a where-clause like `where T: 'c`. This where-clause means "it is safe to create a reference with lifetime `'c` to the type `T`". But in fact we don't create any such reference, so the where-clause should not be needed. It is only needed because used the convenient-but-sometimes-incorrect workaround of adding `+ 'c` to the bounds of our `impl Trait`.
108
108
109
109
Just as before, this error is obscure, touching on the more complex aspects of Rust's type system. Unlike before, there is no easy fix! This problem in fact occurred frequently in the compiler, leading to an [obscure workaround called the `Captures` trait](https://github.com/rust-lang/rust/issues/34511#issuecomment-373423999). Gross!
110
110
@@ -154,10 +154,10 @@ The new explicit syntax is called a "use bound": `impl Trait + use<'x, T>`, for
154
154
In Rust 2024, the default is that the hidden type for a return-position `impl Trait` values use **any** generic parameter that is in scope, whether it is a type or a lifetime. This means that the initial example of this blog post will compile just fine in Rust 2024 ([try it yourself by setting the Edition in the Playground to 2024](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=d366396da2fbd5334b7560c3dfb3290b)):
0 commit comments