Search code examples
genericsrustlifetimehigher-order-functions

Type is not generic enough error when wrapping a function with parameters with lifetimes


I have a generic higher-order function in an external library that takes in a FnMut, wraps it, and returns similar to this:

fn foo<T>(mut f:impl FnMut(T) -> T) -> impl FnMut(T)-> T{
    move |t|f(t)
}

I decided to write a wrapper to handle a specific case:

fn bar(f:impl FnMut(&str) -> &str) -> impl FnMut(&str) -> &str{
    foo(f)
}

However, the compiler errored out saying that one of the types wasn't general enough:

error[E0308]: mismatched types
 --> src/main.rs:4:39
  |
1 | fn foo<T>(mut f:impl FnMut(T) -> T) -> impl FnMut(T)-> T{
  |                                        -----------------
  |                                        |
  |                                        the expected opaque type
  |                                        the found opaque type
...
4 | fn bar(f:impl FnMut(&str) -> &str) -> impl FnMut(&str) -> &str{
  |                                       ^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
  |
  = note: expected associated type `<impl FnMut<(&str,)> as FnOnce<(&str,)>>::Output`
             found associated type `<impl FnMut<(&str,)> as FnOnce<(&str,)>>::Output`

It compiles perfectly fine if I use a type without lifetimes like i32, and writing out the HRTBs doesn't change the error. Best I can tell, the compiler is having trouble matching the lifetimes of the function outputs, but I can't figure out why it's having trouble and how to work around it.

Playground


Solution

  • Unfortunately, rust doesn't currently allow you to create a general enough foo for your bar example.

    But if you modify bar so that the caller chooses the lifetime of f, instead of it being generic over all lifetimes, you can make it work with the current foo:

    fn foo<T>(mut f: impl FnMut(T) -> T) -> impl FnMut(T)-> T {
        move |t| f(t)
    }
    
    fn bar<'a>(f: impl FnMut(&'a str) -> &'a str) -> impl FnMut(&'a str) -> &'a str {
        foo(f)
    }
    

    This is technically more restrictive, but for most use cases, it should be equivalent.