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: src/early-late-parameters.md
+47-23Lines changed: 47 additions & 23 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -11,7 +11,8 @@ introduced: [Intermingled parameter lists] and [Intermingled parameter lists, ta
11
11
12
12
## What does it mean to be "early" bound or "late" bound
13
13
14
-
Every function definition has a corresponding ZST that implements the `Fn*` traits known as a [function item type][function_item_type]. This part of the chapter will talk a little bit about the "desugaring" of function item types as it is useful context for explaining the difference between early bound and late bound generic parameters.
14
+
Every function definition has a corresponding ZST that implements the `Fn*` traits known as a [function item type][function_item_type].
15
+
This part of the chapter will talk a little bit about the "desugaring" of function item types as it is useful context for explaining the difference between early bound and late bound generic parameters.
15
16
16
17
Let's start with a very trivial example involving no generic parameters:
17
18
@@ -51,7 +52,8 @@ impl<T: Sized> Fn<(T,)> for FooFnItem<T> {
51
52
}
52
53
```
53
54
54
-
Note that the function item type `FooFnItem` is generic over some type parameter `T` as defined on the function `foo`. However, not all generic parameters defined on functions are also defined on the function item type as demonstrated here:
55
+
Note that the function item type `FooFnItem` is generic over some type parameter `T` as defined on the function `foo`.
56
+
However, not all generic parameters defined on functions are also defined on the function item type as demonstrated here:
55
57
```rust
56
58
fnfoo<'a, T:Sized>(a:&'aT) ->&'aT {
57
59
# a
@@ -74,9 +76,10 @@ Generic parameters not all being defined on the function item type means that th
74
76
1. Naming the function (e.g. `let a = foo;`) the arguments for `FooFnItem` are provided.
75
77
2. Calling the function (e.g. `a(&10);`) any parameters defined on the builtin impl are provided.
76
78
77
-
This two-step system is where the early vs late naming scheme comes from, early bound parameters are provided in the *earliest* step (naming the function), whereas late bound parameters are provided in the *latest* step (calling the function).
79
+
This two-step system is where the early vs late naming scheme comes from, early bound parameters are provided in the *earliest* step (naming the function), whereas late bound parameters are provided in the *latest* step (calling the function).
78
80
79
-
Looking at the desugaring from the previous example we can tell that `T` is an early bound type parameter and `'a` is a late bound lifetime parameter as `T` is present on the function item type but `'a` is not. See this example of calling `foo` annotated with where each generic parameter has an argument provided:
81
+
Looking at the desugaring from the previous example we can tell that `T` is an early bound type parameter and `'a` is a late bound lifetime parameter as `T` is present on the function item type but `'a` is not.
82
+
See this example of calling `foo` annotated with where each generic parameter has an argument provided:
80
83
```rust
81
84
fnfoo<'a, T:Sized>(a:&'aT) ->&'aT {
82
85
# a
@@ -98,13 +101,15 @@ my_func(&String::new());
98
101
99
102
### Higher ranked function pointers and trait bounds
100
103
101
-
A generic parameter being late bound allows for more flexible usage of the function item. For example if we have some function `foo` with an early bound lifetime parameter and some function `bar` with a late bound lifetime parameter `'a` we would have the following builtin `Fn` impls:
104
+
A generic parameter being late bound allows for more flexible usage of the function item.
105
+
For example if we have some function `foo` with an early bound lifetime parameter and some function `bar` with a late bound lifetime parameter `'a` we would have the following builtin `Fn` impls:
102
106
```rust,ignore
103
107
impl<'a> Fn<(&'a String,)> for FooFnItem<'a> { /* ... */ }
104
108
impl<'a> Fn<(&'a String,)> for BarFnItem { /* ... */ }
105
109
```
106
110
107
-
The `bar` function has a strictly more flexible signature as the function item type can be called with a borrow with *any* lifetime, whereas the `foo` function item type would only be callable with a borrow with the same lifetime on the function item type. We can show this by simply trying to call `foo`'s function item type multiple times with different lifetimes:
111
+
The `bar` function has a strictly more flexible signature as the function item type can be called with a borrow with *any* lifetime, whereas the `foo` function item type would only be callable with a borrow with the same lifetime on the function item type.
112
+
We can show this by simply trying to call `foo`'s function item type multiple times with different lifetimes:
108
113
109
114
```rust
110
115
// The `'a: 'a` bound forces this lifetime to be early bound.
@@ -125,9 +130,12 @@ f(&String::new());
125
130
f(&String::new());
126
131
```
127
132
128
-
In this example we call `foo`'s function item type twice, each time with a borrow of a temporary. These two borrows could not possible have lifetimes that overlap as the temporaries are only alive during the function call, not after. The lifetime parameter on `foo` being early bound requires all callers of `f` to provide a borrow with the same lifetime, as this is not possible the borrow checker errors.
133
+
In this example we call `foo`'s function item type twice, each time with a borrow of a temporary.
134
+
These two borrows could not possible have lifetimes that overlap as the temporaries are only alive during the function call, not after.
135
+
The lifetime parameter on `foo` being early bound requires all callers of `f` to provide a borrow with the same lifetime, as this is not possible the borrow checker errors.
129
136
130
-
If the lifetime parameter on `foo` was late bound this would be able to compile as each caller could provide a different lifetime argument for its borrow. See the following example which demonstrates this using the `bar` function defined above:
137
+
If the lifetime parameter on `foo` was late bound this would be able to compile as each caller could provide a different lifetime argument for its borrow.
138
+
See the following example which demonstrates this using the `bar` function defined above:
131
139
132
140
```rust
133
141
# fnfoo<'a: 'a>(b:&'aString) ->&'aString { b }
@@ -143,7 +151,8 @@ b(&String::new());
143
151
b(&String::new());
144
152
```
145
153
146
-
This is reflected in the ability to coerce function item types to higher ranked function pointers and prove higher ranked `Fn` trait bounds. We can demonstrate this with the following example:
154
+
This is reflected in the ability to coerce function item types to higher ranked function pointers and prove higher ranked `Fn` trait bounds.
155
+
We can demonstrate this with the following example:
147
156
```rust
148
157
// The `'a: 'a` bound forces this lifetime to be early bound.
149
158
fnfoo<'a: 'a>(b:&'aString) ->&'aString { b }
@@ -170,7 +179,8 @@ fn higher_ranked_fn_ptr() {
170
179
}
171
180
```
172
181
173
-
In both of these cases the borrow checker errors as it does not consider `foo_fn_item` to be callable with a borrow of any lifetime. This is due to the fact that the lifetime parameter on `foo` is early bound, causing `foo_fn_item` to have a type of `FooFnItem<'_>` which (as demonstrated by the desugared `Fn` impl) is only callable with a borrow of the same lifetime `'_`.
182
+
In both of these cases the borrow checker errors as it does not consider `foo_fn_item` to be callable with a borrow of any lifetime.
183
+
This is due to the fact that the lifetime parameter on `foo` is early bound, causing `foo_fn_item` to have a type of `FooFnItem<'_>` which (as demonstrated by the desugared `Fn` impl) is only callable with a borrow of the same lifetime `'_`.
174
184
175
185
### Turbofishing in the presence of late bound parameters
The above example errors as the lifetime parameter `'a` is late bound and so cannot be instantiated as part of the "naming a function" step. If we make the lifetime parameter early bound we will see this code start to compile:
201
+
The above example errors as the lifetime parameter `'a` is late bound and so cannot be instantiated as part of the "naming a function" step.
202
+
If we make the lifetime parameter early bound we will see this code start to compile:
192
203
```rust
193
204
fnfoo<'a: 'a>(b:&'au32) ->&'au32 { b }
194
205
195
206
letf/* : FooFnItem<'static> */=foo::<'static>;
196
207
```
197
208
198
-
What the current implementation of the compiler aims to do is error when specifying lifetime arguments to a function that has both early *and* late bound lifetime parameters. In practice, due to excessive breakage, some cases are actually only future compatibility warnings ([#42868](https://github.com/rust-lang/rust/issues/42868)):
209
+
What the current implementation of the compiler aims to do is error when specifying lifetime arguments to a function that has both early *and* late bound lifetime parameters.
210
+
In practice, due to excessive breakage, some cases are actually only future compatibility warnings ([#42868](https://github.com/rust-lang/rust/issues/42868)):
199
211
- When the amount of lifetime arguments is the same as the number of early bound lifetime parameters a FCW is emitted instead of an error
200
212
- An error is always downgraded to a FCW when using method call syntax
Even when specifying enough lifetime arguments for both the late and early bound lifetime parameter, these arguments are not actually used to annotate the lifetime provided to late bound parameters. We can demonstrate this by turbofishing `'static` to a function while providing a non-static borrow:
302
+
Even when specifying enough lifetime arguments for both the late and early bound lifetime parameter, these arguments are not actually used to annotate the lifetime provided to late bound parameters.
303
+
We can demonstrate this by turbofishing `'static` to a function while providing a non-static borrow:
291
304
```rust
292
305
structFoo;
293
306
@@ -302,7 +315,8 @@ This compiles even though the `&String::new()` function argument does not have a
302
315
303
316
### Liveness of types with late bound parameters
304
317
305
-
When checking type outlives bounds involving function item types we take into account early bound parameters. For example:
318
+
When checking type outlives bounds involving function item types we take into account early bound parameters.
319
+
For example:
306
320
307
321
```rust
308
322
fnfoo<T>(_:T) {}
@@ -315,9 +329,11 @@ fn bar<T>() {
315
329
}
316
330
```
317
331
318
-
As the type parameter `T` is early bound, the desugaring of the function item type for `foo` would look something like `struct FooFnItem<T>`. Then in order for `FooFnItem<T>: 'static` to hold we must also require `T: 'static` to hold as otherwise we would wind up with soundness bugs.
332
+
As the type parameter `T` is early bound, the desugaring of the function item type for `foo` would look something like `struct FooFnItem<T>`.
333
+
Then in order for `FooFnItem<T>: 'static` to hold we must also require `T: 'static` to hold as otherwise we would wind up with soundness bugs.
319
334
320
-
Unfortunately, due to bugs in the compiler, we do not take into account early bound *lifetimes*, which is the cause of the open soundness bug [#84366](https://github.com/rust-lang/rust/issues/84366). This means that it's impossible to demonstrate a "difference" between early/late bound parameters for liveness/type outlives bounds as the only kind of generic parameters that are able to be late bound are lifetimes which are handled incorrectly.
335
+
Unfortunately, due to bugs in the compiler, we do not take into account early bound *lifetimes*, which is the cause of the open soundness bug [#84366](https://github.com/rust-lang/rust/issues/84366).
336
+
This means that it's impossible to demonstrate a "difference" between early/late bound parameters for liveness/type outlives bounds as the only kind of generic parameters that are able to be late bound are lifetimes which are handled incorrectly.
321
337
322
338
Regardless, in theory the code example below *should* demonstrate such a difference once [#84366](https://github.com/rust-lang/rust/issues/84366) is fixed:
323
339
```rust
@@ -341,17 +357,20 @@ fn bar<'b>() {
341
357
342
358
### Must be a lifetime parameter
343
359
344
-
Type and Const parameters are not able to be late bound as we do not have a way to support types such as `dyn for<T> Fn(Box<T>)` or `for<T> fn(Box<T>)`. Calling such types requires being able to monomorphize the underlying function which is not possible with indirection through dynamic dispatch.
360
+
Type and Const parameters are not able to be late bound as we do not have a way to support types such as `dyn for<T> Fn(Box<T>)` or `for<T> fn(Box<T>)`.
361
+
Calling such types requires being able to monomorphize the underlying function which is not possible with indirection through dynamic dispatch.
345
362
346
363
### Must not be used in a where clause
347
364
348
-
Currently when a generic parameter is used in a where clause it must be early bound. For example:
365
+
Currently when a generic parameter is used in a where clause it must be early bound.
366
+
For example:
349
367
```rust
350
368
# traitTrait<'a> {}
351
369
fnfoo<'a, T:Trait<'a>>(_:&'aString, _:T) {}
352
370
```
353
371
354
-
In this example the lifetime parameter `'a` is considered to be early bound as it appears in the where clause `T: Trait<'a>`. This is true even for "trivial" where clauses such as `'a: 'a` or those implied by wellformedness of function arguments, for example:
372
+
In this example the lifetime parameter `'a` is considered to be early bound as it appears in the where clause `T: Trait<'a>`.
373
+
This is true even for "trivial" where clauses such as `'a: 'a` or those implied by wellformedness of function arguments, for example:
355
374
```rust
356
375
fnfoo<'a: 'a>(_:&'aString) {}
357
376
fnbar<'a, T: 'a>(_:&'aT) {}
@@ -375,9 +394,12 @@ f(&String::new());
375
394
376
395
At *some point* during type checking an error should be emitted for this code as `String` does not implement `Trait` for any lifetime.
377
396
378
-
If the lifetime `'a` were late bound then this becomes difficult to check. When naming `foo` we do not know what lifetime should be used as part of the `T: Trait<'a>` trait bound as it has not yet been instantiated. When coercing the function item type to a function pointer we have no way of tracking the `String: Trait<'a>` trait bound that must be proven when calling the function.
397
+
If the lifetime `'a` were late bound then this becomes difficult to check.
398
+
When naming `foo` we do not know what lifetime should be used as part of the `T: Trait<'a>` trait bound as it has not yet been instantiated.
399
+
When coercing the function item type to a function pointer we have no way of tracking the `String: Trait<'a>` trait bound that must be proven when calling the function.
379
400
380
-
If the lifetime `'a` is early bound (which it is in the current implementation in rustc), then the trait bound can be checked when naming the function `foo`. Requiring parameters used in where clauses to be early bound gives a natural place to check where clauses defined on the function.
401
+
If the lifetime `'a` is early bound (which it is in the current implementation in rustc), then the trait bound can be checked when naming the function `foo`.
402
+
Requiring parameters used in where clauses to be early bound gives a natural place to check where clauses defined on the function.
381
403
382
404
Finally, we do not require lifetimes to be early bound if they are used in *implied bounds*, for example:
383
405
```rust
@@ -388,11 +410,13 @@ f(&String::new());
388
410
f(&String::new());
389
411
```
390
412
391
-
This code compiles, demonstrating that the lifetime parameter is late bound, even though `'a` is used in the type `&'a T` which implicitly requires `T: 'a` to hold. Implied bounds can be treated specially as any types introducing implied bounds are in the signature of the function pointer type, which means that when calling the function we know to prove `T: 'a`.
413
+
This code compiles, demonstrating that the lifetime parameter is late bound, even though `'a` is used in the type `&'a T` which implicitly requires `T: 'a` to hold.
414
+
Implied bounds can be treated specially as any types introducing implied bounds are in the signature of the function pointer type, which means that when calling the function we know to prove `T: 'a`.
392
415
393
416
### Must be constrained by argument types
394
417
395
-
It is important that builtin impls on function item types do not wind up with unconstrained generic parameters as this can lead to unsoundness. This is the same kind of restriction as applies to user written impls, for example the following code results in an error:
418
+
It is important that builtin impls on function item types do not wind up with unconstrained generic parameters as this can lead to unsoundness.
419
+
This is the same kind of restriction as applies to user written impls, for example the following code results in an error:
0 commit comments