Here's the compilation error. I don't understand what is meant by "some specific lifetime '2". Is "some specific lifetime" different from "any lifetime"?
error: implementation of `FnOnce` is not general enough
ββΈ src/main.rs:29:16
β
29 β Ok(pool.get_or_insert_with(buffer, LeanString::from).clone())
β βββββββββββββββββββββββββββββββββββββββββββββββββ implementation of `FnOnce` is not general enough
β
β note: `fn(&'2 str) -> LeanString {<LeanString as From<&'2 str>>::from}` must implement `FnOnce<(&'1 str,)>`, for any lifetime `'1`...
β° note: ...but it actually implements `FnOnce<(&'2 str,)>`, for some specific lifetime `'2`
Inference of closure types is special in the sense that they are inferred for multiple lifetimes with HRTB. |s| LeanString::from(s) is therefore inferred something along the lines of: for<'a> dyn FnOnce<&'a str, Output = LeanString>, whereas LeanString::from is inferred for a single lifetime (no HRTB).
About get_or_insert_with(&mut self, value: &Q, f: F):
&Q is &str.
F is FnOnce(&Q) -> T, which has an elided lifetime (i.e., F is FnOnce(&'_ Q) -> T).
FnOnce(&'_ Q) -> T and for<'a> FnOnce(&'a Q) -> T are different.
FnOnce(&'_ Q) -> T accepts some specific lifetime only.
for<'a> FnOnce(&'a Q) -> T accepts all lifetimes.
T is LeanString.
So, actual get_or_insert_with() is something like fn<'elided1, 'elided2, 'elided3>(&'elided1 mut self, &'elided2 str, FnOnce(&'elided3 str) -> LeanString)?
As I understand it, the problem is not with get_or_insert_with per se but with it being used within a borrowing context (the closure from with_borrow_mut) that makes it's parameter's lifetime unnameable, hence the need for HRTB.
for<'elided3> FnOnce(&'elided3 str) is the un-elided form of FnOnce(&str) β the FnOnce function signature gets its own lifetime elision separate from get_or_insert_with(). And this for<'elided3> is why the error you get is βnot general enoughβ instead of some other lifetime mismatch.
I'm not sure why the signature isn't defined as
pub fn get_or_insert_with<'a, Q, F>(&mut self, value: &'a Q, f: F) -> &T
where
T: Borrow<Q>,
Q: Hash + Eq + ?Sized,
F: FnOnce(&'a Q) -> T,
which, if it was, would solve your problem.
The reason why using a closure is different is that the closure |s| LeanString::from(s)can be typed as a function of signature for<'a> FnOnce(&'a str), but LeanString::from is actually <LeanString as From<&'a str>>::from β picking the trait impl picks a particular lifetime, so any given function value you get from that expression is a conversion from a reference with that particular lifetime, not any lifetime.
The cases where you needfor<'a> on a function type/trait are the same as the cases where you need to write fn foo<'a> to add a lifetime parameter to a named function (plus the cases where the self lifetime would be used, but function types/traits don't have that rule).
I believe that is always the case, even in function bodies. Same with fn(..).
fn f(_: &'static str) {}
fn main() {
// error[E0308]: mismatched types
// i.e. it expected `for<'a> fn(&'a str)` and not a single inferred lifetime
let _: fn(&str) = f;
}
For those, when you need to mention the same lifetime more than once.
for<'a> FnOnce(&'a str, &'a str) -> &'a str
Other traits don't have the sugar and you always have to use for<'a> if you want the higher-ranked bound or type.
// error[E0637]: `&` without an explicit lifetime name cannot be used here
fn fails<T: PartialEq<&str>>(_: T) {}
fn compiles<T: for<'a> PartialEq<&'a str>>(_: T) {}
let local = String::new();
// `dyn PartialEq<&'lifetime str>` for one single inferred `'lifetime`
let obj: &dyn PartialEq<&str> = &local;
// The higher-ranked type you may have meant instead
let obj: &dyn for<'a> PartialEq<&'a str> = &local;
Contrast the last example with the fn(&str) example at the top of this comment.