# Closure return-type projection lifetime issue
## The problem
Currently, the following code compiles:
```rust=
fn assert_static_closure<R, F: 'static + FnOnce() -> R>(_val: F) {}
fn foo<'a>() {
let closure = || -> &'a str { "" };
assert_static_closure(closure);
}
```
This code creates a closure with no captures or arguments, which returns `&'a str`. This closure:
* implements `FnOnce() -> &'a str`
* Outlives `'static`.
Unfortunately, the these two facts are unsound when combined with projection lifetime inference (described in RFC 1214). In the projection:
```rust
<closure_type as FnOnce<()>>::Output
```
Since both `closure_type: 'static'` and `(): 'static'` hold, RFC 1214 lets us conclude that `<closure_type as FnOnce<()>>::Output: 'static` holds - that is, the closure return type outlives `'static`. However, this is *not* actually true - the closure return type is `&'a str` for some caller-provided `'a'`. As shown in https://github.com/rust-lang/rust/issues/84366#issue-862721401, this can be turned into a safe lifetime-transmute (specifically a use-after-free).
The same issue applies to generators, with the `Yield` and `Return` associated types instead of `Output`
## Closures and structs
Closure impls are special - they don't go through an `impl` item like user-provided trait implementations do. Rustc disallows writing an equivalent impl by hand:
```rust=
trait CustomFn<A> {
type Assoc;
}
struct Foo;
impl<'a, T> CustomFn<()> for Foo { //~ ERROR unconstrained lifetime/type parameter 'a
type Assoc = (&'a str, T);
}
```
The compiler will disallow these unconstrained usage of `'a` and `T`, where the type or lifetime is essentially pulled out of thin air. This is what makes the inference rule in RFC 1214 sound - when writing an impl, any lifetimes or generic parameters used in an associated type must also be used in the self-type or a projection argument.
## The fix
In order to fix this, we need to ensure that any lifetimes or generic parameters that appear in a closure return type appear in *either* the argument types or the closure itself.
### Good cases
These closures follow this pattern, and will be completely unaffected by our fix:
```rust=
fn foo<'a, T: Copy>(arg: &'a T) {
let closure_type_contains_lifetime = || -> &'a T {
let captured_arg: &'a T = arg;
captured_arg
};
let closure_argument_contains_lifetime = |closure_arg: &'a T| -> &'a T {
closure_arg
};
}
```
The closure `closure_type_contains_lifetime` captures the variable `arg`, so the closure type itself will contain the lifetime `'a'`. The desuragred closure will look something like:
```rust=
struct ClosureImpl<'a, T> {
upvar: &'a T
}
impl<'a, T> FnOnce<()> for ClosureImpl<'a> {
type Output = &'a T;
...
}
```
This hand-written impl would be accepted by the compiler, as the lifetime `'a'` appears in the self type `ClosureImpl`.
Similarly, `closure_argument_contains_lifetime` desugars to something that looks like:
```rust=
struct ClosureImpl {}
impl<'a, T> FnOnce<&'a T> for ClosureImpl {
type Output = &'a T;
...
}
```
This hand-written impl would also be accepted by the compiler.
### Bad cases
When we encounter a closure like `|| -> &'a str { "" };`, we need to effectively insert the lifetime from the return type into the closure self type. The closure will (very roughly) desuar to something like:
```rust=
struct ClosureImpl<'a, T> {
// Some imaginary type that makes 'a and T be considered when
// determines 'outlives' relationships, but not for auto-traits
// or dropck
inserted_lifetime: PhantomDataButNoDropckOrAutoTraits<&'a T>
}
impl<'a, T> FnOnce<()> for ClosureImpl<'a, T> {
type Output = &'a T;
}
```
Note - closure generics are special, and can 'inherit' generic parameters from the parent function. There is no equivalent for hand-written structs.
As a result, the closure itself is no longer `'static`, despite having no captures! This is an unfortunate but unavoidable consequence of the way that projections work. We need to prevent `<ClosureImpl as FnOnce<Arguments>>::Output: 'static'` from holding. The argument types can be directly written by the user (as in `T: FnOnce() -> R`), so they cannot be modified. The only type the compiler has control over is the unnameable closure type, so we must modify it.
## Breakage
Unfortunately, this turns out to cause a large amount of breakage in practice. While this discussion has focused on closures, generators (in the form of `async` fns and blocks) turned out to account for a large portion of the breakage detected by Crater: https://github.com/rust-lang/rust/pull/84385#issuecomment-1272886410
Crater found 13K regressions. This is an overcount, as I performed the crater run with a PR that implemented an overly conservative version of this check (which did not consider lifetimes in FnOnce arguments). However, I've identified many legitimate regresssions in popular crates, which will still occur with a more careful implementation.
* `http-body-util`
The affected code pattern is:
```rust
struct MapErr<A, F: FnOnce(A)>;
fn map_err<A, F: FnOnce(A)>(closure: F) {
MapErr(closure)
}
// ...
fn require_static<T: 'static>(_val: T) {}
fn map_never<E, F: FnOnce(!) -> E>(val: F) {
require_static(map_err(f))
}
```
This code
## Rejected alternatives
### 'Disable' the lifetime inference from RFC 1214 for *just* `FnOnce/FnMut/Fn`
Since closure types can't be named directly, it might seem as though we could fix this problem by adjusting the `Fn*` traits. For example, we might decide that `T: 'static + FnOnce() -> R` should **not** imply that `R: 'static'` holds.
Even if this was acceptable from a language design or ecosystem breakge perspective, it turns out to be impossible to isolate the impact of lifetime inference in this way. Consider the following code:
```rust=
// good_crate
trait GoodTrait {
type GoodAssoc;
}
fn assert_static<T: 'static>(_val: T) {}
fn good_projection<T: 'static + GoodTrait>(arg: <T as GoodTrait>::GoodAssoc) {
assert_static(arg);
}
```
This code has absolutely nothing to do with closures, and must continue to compile. However, another crate might write:
```rust=
impl<F> GoodTrait for F where F: FnOnce() {
type GoodAssoc = F::Output;
}
fn call_good_projection<'a>() {
let bad_closure = || -> &'a str { "" };
good_crate::good_projection(bad_closure)
}
```
This code ends up performing a 'bad' projection by going through another crate. 'Disabling' the lifetime inference for `Fn*` traits could affect other trait impls, 'nested' in this way at an arbitrary depth. The closure might be nested within several layers of wrapper types, and only trigger a compilation error when some innocuous-seeming function from another crate is called.
### Ban closures with return types with unconstrained lifetimes or generic parameters
While the `|| -> &'a str'` closure seems artificial, these kinds of closures can turn out to be very useful. For example, `http-body-util` contains a closure that looks like:
```rust
fn foo<E> {
let convert_error = |argument: !| -> E => { match argument {} }
}
```
This closure converts from an uninhabited type (`!` in this case) to an arbitrary type `E`. If this function is called with a generic argument that contains a lifetime (e.g. `foo::<&'a str>'`) we'll encounter the same issue. Special-casing uninhabited arguments will not solve the problem, as `|| -> E { unimplemented!() }` could be used instead.