Description
Given the following code: https://godbolt.org/z/6ndY3zx7q
pub fn next<'buf, 'read, T>(
r: &'read mut i32,
f: impl FnOnce(&mut &'read &'buf i32) -> T,
) -> T { todo!() }
pub fn tagged<'buf, 'read, T>(
r: &'read mut i32,
f: impl FnOnce(&mut &'read &'buf i32) -> T,
) -> T {
next(r, move |e| inner(e, f))
}
pub fn inner<'buf, 'read, T>(
r: &'read mut &&'buf i32,
f: impl FnOnce(&mut &'read &'buf i32) -> T,
) -> T { todo!() }
The current output is:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'buf` due to conflicting requirements
--> <source>:10:22
|
10 | next(r, move |e| inner(e, f))
| ^^^^^
|
note: first, the lifetime cannot outlive the lifetime `'buf` as defined on the function body at 6:15...
--> <source>:6:15
|
6 | pub fn tagged<'buf, 'read, T>(
| ^^^^
note: ...but the lifetime must also be valid for the lifetime `'read` as defined on the function body at 6:21...
--> <source>:6:21
|
6 | pub fn tagged<'buf, 'read, T>(
| ^^^^^
note: ...so that the types are compatible
--> <source>:10:22
|
10 | next(r, move |e| inner(e, f))
| ^^^^^
= note: expected `for<'r> FnOnce<(&'r mut &&i32,)>`
found `for<'r> FnOnce<(&'r mut &'read &'buf i32,)>`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0495`.
Compiler returned: 1
I run into this (the "bad variance diagonistic") whenever I'm doing anything particularly difficult with lifetimes. Even though most people would say I'm a very advanced Rust user, this particular class of diagnostics is extremely time-consuming for me to unwind. This error always means one of two things:
- I'm missing an explicit lifetime somewhere. The diagnostic provides no hints as to where it is and I need to hunt through the crate to find it.
- The API I've designed is impossible to implement because it causes the borrow checker to prove a contradiction.
Both of these are pretty frustrating, and I have to use my personal heuristics to resolve them. I've honestly just gotten used to the pain and am only filing this because @estebank nudged me. I don't want to imagine how a newbie feels when they see this error.
I don't have... obvious suggestions for improving the diagnostic, since I don't actually know how the current borrow checker implementation aggregates information. After all, it would be very hard for it to guess which lifetime I missed, since it would need to explore many more paths.
However, there may be hope. The pretty-printed types are really unhelpful, which points to an underlying issue:
= note: expected `for<'r> FnOnce<(&'r mut &&i32,)>` // Lol what's &&i32?
found `for<'r> FnOnce<(&'r mut &'read &'buf i32,)>`
There's a bunch of different lifetimes with similar names involved. Which 'buf
is it referring to? What are the anonymous lifetimes in for<'r> FnOnce<(&'r mut &&i32,)>
? The compiler has clearly given these regions anonymous names, so it might be worth highlighting them with something like "Call this region '1
" or similar, so that they can be printed in the error?
Perhaps it might also be useful to add a note that lists out the incompatible constraints as a where clause, once we've given the lifetime inference variables names. It would be easier to parse at a glance than trying to build it up from prose.
Finally, I wonder if it's helpful for the compiler to say "btw, I can't do this reduction I wanted to make because this lifetime isn't covariant".