Search code examples
rustlifetimelifetime-scopingrust-async-stdrust-futures

"one type is more general than the other" error in Rust while types are identical


I have the following code

use std::future::Future;
fn main() {
    handle(Test::my_func);
}

fn handle<Fut>(fun: for<'r> fn(&'r mut Test) -> Fut) -> bool
where
    Fut: Future<Output = ()>,
{
    true
}

struct Test {}

impl Test {
    pub async fn my_func<'r>(&'r mut self) -> () {
        ()
    }
}

Also, you can run it online on Rust Playground.

The following error appears:

error[E0308]: mismatched types
  --> src/main.rs:4:12
   |
4  |     handle(Test::my_func);
   |            ^^^^^^^^^^^^^ one type is more general than the other
...
17 |     pub async fn my_func<'r>(&'r mut self) -> () {
   |                                               -- checked the `Output` of this `async fn`, found opaque type
   |
   = note: while checking the return type of the `async fn`
   = note: expected fn pointer `for<'r> fn(&'r mut Test) -> impl Future`
              found fn pointer `for<'r> fn(&'r mut Test) -> impl Future`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

Now, this is really weird because it clearly says that it's getting what it's expecting. I don't understand which type is more general than which. I don't understand lifetimes enough to debug this code. Could someone spare some more insight into this?


Solution

  • Sometimes you can get a better error from the nightly compiler:

    16 |     pub async fn my_func<'r>(&'r mut self) -> () {
       |                                               ^^ checked the `Output` of this `async fn`, found opaque type
       = note: expected fn pointer `for<'r> fn(&'r mut Test) -> impl Future`
                  found fn pointer `for<'r> fn(&'r mut Test) -> impl for<'r> Future`
    

    In simple terms, this means that you expect fun to be a function which returns a struct implementing the Future trait, but with an implicit + 'static bound, meaning the struct cannot have fields which are only alive as long as 'r.

    However, Test::my_func does want to refer to &mut 'r self, and because of that the program is rejected by the complier.

    You could imagine something like for<'r> fn(&'r mut Test) -> (impl Future + 'r), but this is currently not something that rustc accepts, and AFAIK there isn't any way to do this (without unsafe or boxing everything).

    Depending on your use case, you might be able to get away with:

    fn handle<'a, Fut>(fun: impl Fn(&'a mut Test) -> Fut) -> bool
    where
        Fut: Future<Output = ()> + 'a,
    {
        true
    }
    
    

    this works because we require a single lifetime ('a), which we can name for the Fut: Future<Output = ()> + 'a bound, instead of any lifetime (for<'r> ..) which we cannot name in that bound, but this is a lot more restrictive.