Difference between elided lifetimes and HRTB - why are they incompatible

Consider this example (playground) in which I pass a function in two different ways:

  • by-value: parse_with(my_parser)
  • wrapped with a closure: parse_with(|input| my_parser(input))

These seem to be identical , but there are certain cases where the closure version does not compile. Taking the error message from the link above:

error[E0631]: type mismatch in closure arguments
  --> src/main.rs:34:13
   |
21 | fn parse_with<F: Parser>(parser: F) -> Result<F::Output, Error> {
   |    ----------    ------ required by this bound in `parse_with`
...
34 |     let _ = parse_with(|input| my_parser(input));
   |             ^^^^^^^^^^ ------------------------ found signature of `fn(&Input) -> _`
   |             |
   |             expected signature of `for<'a> fn(&'a Input) -> _`
   |
   = note: required because of the requirements on the impl of `Parser` for `[closure@src/main.rs:34:24: 34:48]`

error[E0271]: type mismatch resolving `for<'a> <[closure@src/main.rs:34:24: 34:48] as std::ops::FnOnce<(&'a Input,)>>::Output == std::result::Result<_, Error>`
  --> src/main.rs:34:13
   |
34 |     let _ = parse_with(|input| my_parser(input));
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter 'a, found concrete lifetime
   |
   = note: required because of the requirements on the impl of `Parser` for `[closure@src/main.rs:34:24: 34:48]`

It complains that the closure (type fn(&'_ Input) -> Result<_, Error> does not satisfy for<'a> FnOnce(&'a Input) -> Result<_, Error>. So in this example, either the elided lifetime does not satisfy the HRTB, even though the lifetime isn't used or bound in any other way in the function signature, or the compiler is failing to reason about this case.

It also has something to do with the indirection in the type system created by the trait Parser. This simpler example which does not use such a trait (effectively "inlining" the generic bound onto parse_with) compiles successfully.

Closures lifetime inference is really bad right now, see this issue

2 Likes

Thanks, I found the solution within that issue. The closure's argument type needs to be annotated like |input: &Input| { /* ... */ }, and it will compile successfully.