Difference between `|s| LeanString::from(s)` and `LeanString::from`

Please help me understand why |s| LeanString::from(s) works, but LeanString::from does not. What's the difference between them?

See the code inside fn visit_str() below.

#![feature(hash_set_entry)]

use std::{cell::RefCell, collections::HashSet};

use lean_string::LeanString;
use serde::de::{Error, Visitor};

thread_local! {
    static STRING_POOL: RefCell<HashSet<LeanString>> = <_>::default();
}

struct StringVisitor;

impl<'de> Visitor<'de> for StringVisitor {
    type Value = LeanString;

    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
        formatter.write_str("a string")
    }

    fn visit_str<E>(self, buffer: &str) -> Result<Self::Value, E>
    where
        E: Error,
    {
        STRING_POOL.with_borrow_mut(|pool| {
            // Ok(pool.get_or_insert_with(buffer, |s| LeanString::from(s)).clone())
            Ok(pool.get_or_insert_with(buffer, LeanString::from).clone())
        })
    }
}
[dependencies]
lean_string = "0.5.3"
serde = "1.0.228"

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).

1 Like

I'm trying to understand step by step.

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.

It's actually

fn<'elided1, 'elided2>(
    &'elided1 mut self,
    &'elided2 str,
    impl for<'elided3> FnOnce(&'elided3 str) -> LeanString
) -> &'elided1 LeanString

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.

4 Likes

This is the issue in your logic, they are not different.

1 Like

Is FnOnce(&'_ Q) -> T simply an abbreviation for for<'a> FnOnce(&'a Q) -> T? Why and when do we need to explicitly use for<'a>?

Are there any examples where the meaning of the code changes if for<'a> is omitted?

The cases where you need for<'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).

1 Like

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.

1 Like