Use HRTB when returning impl trait type

I am trying to write a generic compose function which chains two functions f and g together into f.g. This works fine when the arguments and return values are all owned type.

However, when it comes to having a reference in the arguments, the returned closure binds its argument to a specific lifetime, instead of accepting any lifetime.

   = note: `impl FnOnce(&'2 u8) -> u32` must implement `FnOnce<(&'1 u8,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 u8,)>`, for some specific lifetime `'2`

Is there a way to write a generic compose function that works no matter if there is a reference in the arguments? Writing two functions is acceptable to me, but I want to know if there is an elegant way.

Here is an example of the problem I come into. I freeze one part of the compose to easily illustrate the problem. Rust playground

fn f(x: u16) -> u32 {
    x as u32
}

/// This function returns a closure with HRTB
fn compose_ref<T>(g: impl FnOnce(&T) -> u16) -> impl FnOnce(&T) -> u32 {
    |x| f(g(x))
}

/// This function, when T is a reference, does not return a closure with HRTB
fn compose<T>(g: impl FnOnce(T) -> u16) -> impl FnOnce(T) -> u32 {
    |x| f(g(x))
}

/// Check if a closure has HRTB
fn check_hrtb<T>(_: impl for<'a> FnOnce(&'a T) -> u32) {}

pub fn main() {
    let g = |x: &u8| x.clone() as u16;

    // The following line won't compile.
    // check_hrtb(compose(g));

    check_hrtb(compose_ref(g));
}

Please forgive me if this problem is answered somewhere else. I searched in the forum but didn't find any related topic, so I created a new one.

The right impl FnOnce(T) -> u32 can't be a HRTB because T on the right side will be identical to the T on the left impl.

I'm not sure what you try to achieve. Maybe using Cow can help?

fn compose_cow<T>(g: impl FnOnce(Cow<'_, T>) -> u16) -> impl FnOnce(Cow<'_, T>) -> u32
where
    T: Clone,
{
    |x| f(g(x))
}

(Playground)

1 Like

If the runtime overhead of Cow is an issue, you might also use something like deref_owned::GenericCow. But that might not be very idiomatic and be unwieldy (though Cow is also a bit unwieldy perhaps).

I made a mistake in my code example above. The Clone bound is too strict. It should have been:

 fn compose_cow<T>(g: impl FnOnce(Cow<'_, T>) -> u16) -> impl FnOnce(Cow<'_, T>) -> u32
 where
-    T: Clone,
+    T: ToOwned + ?Sized,
 {
     |x| f(g(x))
 }

(Playground)

There's sort-of a way, but it's not at all elegant or zero-cost.

  • You have to cast to fn or box your closures
  • Inference won't help much so your casts and boxing will be pretty explicit
  • Trying to loosen the 'static bounds leads into a lot of dead-ends with the current compiler
  • You're relying on MCP 295 going through so that the coherence leak check warnings don't turn into errors

So probably you want to stick with two functions.

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.