Skip to content

Commit fa09a2c

Browse files
committed
sembr src/early-late-parameters.md
1 parent 539bbfb commit fa09a2c

1 file changed

Lines changed: 47 additions & 23 deletions

File tree

src/early-late-parameters.md

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ introduced: [Intermingled parameter lists] and [Intermingled parameter lists, ta
1111

1212
## What does it mean to be "early" bound or "late" bound
1313

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.
1516

1617
Let's start with a very trivial example involving no generic parameters:
1718

@@ -51,7 +52,8 @@ impl<T: Sized> Fn<(T,)> for FooFnItem<T> {
5152
}
5253
```
5354

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:
5557
```rust
5658
fn foo<'a, T: Sized>(a: &'a T) -> &'a T {
5759
# a
@@ -74,9 +76,10 @@ Generic parameters not all being defined on the function item type means that th
7476
1. Naming the function (e.g. `let a = foo;`) the arguments for `FooFnItem` are provided.
7577
2. Calling the function (e.g. `a(&10);`) any parameters defined on the builtin impl are provided.
7678

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).
7880

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:
8083
```rust
8184
fn foo<'a, T: Sized>(a: &'a T) -> &'a T {
8285
# a
@@ -98,13 +101,15 @@ my_func(&String::new());
98101

99102
### Higher ranked function pointers and trait bounds
100103

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:
102106
```rust,ignore
103107
impl<'a> Fn<(&'a String,)> for FooFnItem<'a> { /* ... */ }
104108
impl<'a> Fn<(&'a String,)> for BarFnItem { /* ... */ }
105109
```
106110

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:
108113

109114
```rust
110115
// The `'a: 'a` bound forces this lifetime to be early bound.
@@ -125,9 +130,12 @@ f(&String::new());
125130
f(&String::new());
126131
```
127132

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.
129136

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:
131139

132140
```rust
133141
# fn foo<'a: 'a>(b: &'a String) -> &'a String { b }
@@ -143,7 +151,8 @@ b(&String::new());
143151
b(&String::new());
144152
```
145153

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:
147156
```rust
148157
// The `'a: 'a` bound forces this lifetime to be early bound.
149158
fn foo<'a: 'a>(b: &'a String) -> &'a String { b }
@@ -170,7 +179,8 @@ fn higher_ranked_fn_ptr() {
170179
}
171180
```
172181

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 `'_`.
174184

175185
### Turbofishing in the presence of late bound parameters
176186

@@ -188,14 +198,16 @@ fn foo<'a>(b: &'a u32) -> &'a u32 { b }
188198
let f /* : FooFnItem<????> */ = foo::<'static>;
189199
```
190200

191-
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:
192203
```rust
193204
fn foo<'a: 'a>(b: &'a u32) -> &'a u32 { b }
194205

195206
let f /* : FooFnItem<'static> */ = foo::<'static>;
196207
```
197208

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)):
199211
- 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
200212
- An error is always downgraded to a FCW when using method call syntax
201213

@@ -287,7 +299,8 @@ Foo::inherent_function::<'static, 'static, 'static>(&(), &());
287299
free_function::<'static, 'static, 'static>(&(), &());
288300
```
289301

290-
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:
291304
```rust
292305
struct Foo;
293306

@@ -302,7 +315,8 @@ This compiles even though the `&String::new()` function argument does not have a
302315

303316
### Liveness of types with late bound parameters
304317

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:
306320

307321
```rust
308322
fn foo<T>(_: T) {}
@@ -315,9 +329,11 @@ fn bar<T>() {
315329
}
316330
```
317331

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.
319334

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.
321337

322338
Regardless, in theory the code example below *should* demonstrate such a difference once [#84366](https://github.com/rust-lang/rust/issues/84366) is fixed:
323339
```rust
@@ -341,17 +357,20 @@ fn bar<'b>() {
341357

342358
### Must be a lifetime parameter
343359

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.
345362

346363
### Must not be used in a where clause
347364

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:
349367
```rust
350368
# trait Trait<'a> {}
351369
fn foo<'a, T: Trait<'a>>(_: &'a String, _: T) {}
352370
```
353371

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:
355374
```rust
356375
fn foo<'a: 'a>(_: &'a String) {}
357376
fn bar<'a, T: 'a>(_: &'a T) {}
@@ -375,9 +394,12 @@ f(&String::new());
375394

376395
At *some point* during type checking an error should be emitted for this code as `String` does not implement `Trait` for any lifetime.
377396

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.
379400

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.
381403

382404
Finally, we do not require lifetimes to be early bound if they are used in *implied bounds*, for example:
383405
```rust
@@ -388,11 +410,13 @@ f(&String::new());
388410
f(&String::new());
389411
```
390412

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`.
392415

393416
### Must be constrained by argument types
394417

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:
396420
```rust
397421
trait Trait {
398422
type Assoc;

0 commit comments

Comments
 (0)