Why does the compiler assume my closure parameter has a specific life time here?

Consider the following code:

fn expects_closure(_closure: impl Fn(&i32)) {}

fn passes_closure(closure: impl Fn(&i32)) {
    // Why is this hint required?    vvvv
    let encapsulated_closure = |a /* : &_ */| closure(a);

    expects_closure(encapsulated_closure);
}

(Playground)

The code compiles if we add the commented type hint a: &_. Otherwise, the compiler complains about the following:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/lib.rs:7:5
  |
7 |     expects_closure(encapsulated_closure);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected trait `for<'a> Fn(&'a i32)`
             found trait `Fn(&i32)`
note: this closure does not fulfill the lifetime requirements
 --> src/lib.rs:5:32
  |
5 |     let encapsulated_closure = |a /* : &_ */| closure(a);
  |                                ^^^^^^^^^^^^^^
note: the lifetime requirement is introduced here
 --> src/lib.rs:1:35
  |
1 | fn expects_closure(_closure: impl Fn(&i32)) {}
  |                                   ^^^^^^^^
help: consider specifying the type of the closure parameters
  |
5 |     let encapsulated_closure = |a: &_| closure(a);
  |                                ~~~~~~~

error: implementation of `FnOnce` is not general enough
 --> src/lib.rs:7:5
  |
7 |     expects_closure(encapsulated_closure);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 i32)` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (lib) due to 2 previous errors

Why do I need to use a type hint here? Why does the compiler assume that without the type hint, the closure parameter a could have a given specific lifetime, instead of any lifetime?

Is this a shortcoming in the compiler, or is there a deeper reason behind this?

This is mostly a shortcoming of the compiler. This also works:

pub fn passes_closure(closure: impl Fn(&i32)) {
    expects_closure(|a| closure(a))
}

I dunno much beyond that.

1 Like

If I recall correctly, higher-ranked inference is undecidable.

But it's definitely also a shortcoming of the compiler, i.e. I feel it could and should do better.

It will also never be 100%, so I'm also strongly in favor of more complete and better ways to override inference. The particular example in this thread has a couple workarounds, but there isn't always a feasible workaround once you start hitting more complicated cases (e.g. closures that need to take any lifetime as in input, then capture that in an output, and the output type is unnameable).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.