Is there any way to declare a From impl that can be used as closure with HRTB?

I have a generic function that takes a closure as argument builder: impl for<'a> FnOnce(&'a String) -> Orange<'a>, and I have a impl<'a> From<&'a String> for Orange<'a> impl. I assumed I could have passed the Orange::from function as builder. But it complains with an error I don't really understand:

16 |     find_orange(Orange::from);
   |     ^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected struct `Orange<'_>`
              found struct `Orange<'a>`

Minimal example:

struct Orange<'a>(&'a String);

impl<'a> From<&'a String> for Orange<'a> {
    fn from(owner: &'a String) -> Self {
        Self(&owner)
    }
}

fn find_orange(builder: impl for<'a> FnOnce(&'a String) -> Orange<'a>) {}

fn main() {
    let owner = String::from("Tarocco");
    let orange = Orange::from(&owner);
    
    // Boom, why doesn't this work?
    find_orange(Orange::from);
}

So is there any way to change the From impl so that it can be used as builder parameter?

It works with an ordinary function:

fn orange_from<'a>(from: &'a String) -> Orange<'a> {
    Orange::from(from)
}

The problem is that the generic lifetime is declared "on" the Orange type rather than "on" the function itself. That is, there is an Orange<'lifetime1>::from method and an Orange<'lifetime2>::from method, and these are two different non-generic methods defined on different types that differ only by a lifetime. What you wanted is a single function that is generic.

Thanks, yes a function works but is there no way to impl From for this? The limit seems rather arbitrary.

There's no way to use From with this particular definition (impl for<'a> FnOnce(&'a String) -> Orange<'a>). From::from will always be a function with no lifetime parameters, because that's what it is in the trait definition

pub trait From<T> {
    pub fn from(T) -> Self;
}

If you use a different trait, it could be possible - for instance, this could work:

pub trait IntoOrange {
    fn into_orange<'a>(&'a self) -> &'a Orange;
}
impl IntoOrange for String { .... }

find_orange(String::into_orange)

It'd work because the into_orange function is generic (and thus matches the FnOnce definition), unlike from.

I think the limitation here is that the compiler has no way to form From::<Orange<'x>>::from into a function generic over <'x>. It theoretically could - I think modifying the compiler to do this could be possible? But right now, the <'x> is a parameter of the trait, and thus needs to be specified in order to even access from. Orange::from doesn't exist as a function to pass in to find_orange without the concrete definition of Orange, which includes a lifetime (and thus the resulting function is not generic over a lifetime, as it's already been decided).

Practically, find_orange(|o| o.into()) should be usable. But I understand that that's not as really nice and pure.

1 Like

FWIW, this is the question of early-bound vs. late-bound lifetimes: when the generic lifetime parameter infects a function call –and is unbounded!1–, then that function will have a higher-order / universal lifetime signature involving it. But when the lifetime parameter comes from an outer level, such as a generic trait (your case) or a generic type, then the lifetime parameter is not higher-order, but fixed (although it can be inferred to take any necessary lifetime).

fn example<'lt> (_: &'lt ()) {}

struct WithLifetime<'lt>(&'lt ());

impl<'lt> WithLifetime<'lt> {
    fn example (_: &'lt ()) {}
}

example::<'_> // : βˆ€'lt fn(&'lt ())
WithLifetime::<'_>::example // : βˆƒ'lt fn(&'lt ())

1 If the lifetime parameter is bounded, then the function loses its higher-order power over that lifetime parameter, as showcased by the following snippet:

fn example<'lt> (_: &'lt mut ())
where /* trivial bound that does nothing but disabling higher-order-ness */
    'lt : 'lt, /* If you comment this out, it compiles */
{}

fn main ()
{
    let f = example;
    f(&mut ()); // Error, temporary value dropped while borrowed.
    f(&mut ());
}

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.