1

I've got a scenario where I want to pass a function pointer (or closure) that takes a mutable reference to a struct, as a way of letting the caller initialize that struct for me.

The (very simplified) code looks like this:

use std::future::Future;

struct Data {
    value: u32
}

async fn initialize(data: &mut Data) {
    // ... do some async work ...
    data.value = 1;
}

fn do_work<Fut>(init: fn(&mut Data) -> Fut) 
where
    Fut: Future<Output = ()>,
{
    pollster::block_on(async {
        let mut data = Data { value:0 };
        init(&mut data).await;
        // ... do other stuff with `data`
    })
}

fn main() {
    do_work(initialize);
}

And I get the compiler error:

error[E0308]: mismatched types
  --> src/main.rs:29:13
   |
29 |     do_work(initialize);
   |     ------- ^^^^^^^^^^ one type is more general than the other
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected fn pointer `for<'a> fn(&'a mut Data) -> _`
                 found fn item `for<'a> fn(&'a mut Data) -> impl Future<Output = ()> {initialize}`
note: function defined here
  --> src/main.rs:17:4
   |
17 | fn do_work<Fut>(init: fn(&mut Data) -> Fut) 
   |    ^^^^^^^      --------------------------

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

but I can not find where I've gone wrong. I'm suspecting there's something whacky happening with Higher-Ranked Trait Bounds since there's a for<'a> in there, but I can't figure out why it's not correct.

Playground link.

If I don't make initialize async, it works just as expected. Maybe there's something going on with Future<Output = ()>?

Edit:

A similar question is "Expected fn item, found a different fn item" when working with function pointers, but as @PitaJ pointed out this is not the same thing as in this case I've got a fn item and a fn pointer and not two fn items, and here there's a generic function output.

Martin Jonsson
  • 139
  • 2
  • 9
  • 3
    note that a `fn pointer` and a `fn item` are not the same because every `fn` defines it's own type which is distinct from every other `fn`. – cafce25 Jan 06 '23 at 19:29
  • 1
    Does this answer your question? ["Expected fn item, found a different fn item" when working with function pointers](https://stackoverflow.com/questions/27895946/expected-fn-item-found-a-different-fn-item-when-working-with-function-pointer) – cafce25 Jan 06 '23 at 19:31
  • 1
    @cafce25 That looks like a very different case. This case has only one fn item, and has no generics for the actual function, just for the output. – PitaJ Jan 06 '23 at 19:42
  • You're right it's probably not a dupe but the case is similar because it's still a mismatch of `fn item` and `fn pointer`, this case gets complicated by the implicit `impl` return type. @PitaJ – cafce25 Jan 06 '23 at 19:45
  • No, that question is about a mismatch between two different fn items, not between a fn item and fn pointer. – PitaJ Jan 06 '23 at 19:46
  • Interesting, you can't cast `initialize as fn(&mut _) -> _` because of the generic return type. – BallpointBen Jan 06 '23 at 19:54
  • It appears to be some combination of HKTB and generic return type. Here's an even more minimal case: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5333ca100a967c2ea3ffc4cfa470fe6a – PitaJ Jan 06 '23 at 19:58
  • 1
    The core problem is that there is an additional constraint with `initialize` that the return type holds a reference to the parameter; that constraint is not communicated with a simple `fn(&mut Data) -> Fut` type. – kmdreko Jan 06 '23 at 20:35
  • 1
    Does that help you understand ? https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=33b25f5369a9486aed27e0404e70c19a, maybe polonius lifetime solve this. – Stargateur Jan 06 '23 at 21:05
  • Thanks for clarifying `fn pointer` and `fn item` for me, @cafce25. While that other question does not answer this one, as @PitaJ pointed out, it illuminated parts of the problem for me! – Martin Jonsson Jan 07 '23 at 05:14
  • Yes, @Stargateur, that solves it! Thank you! Do you want to write an answer, so I can mark it as accepted? If you've got the time, I would appreciate a more in-depth explanation of what was wrong in the first place and why your solution works. My understanding is that the problem in my original code was that the future generated from `initialize` captured the reference `&mut Data` without requiring the lifetime of that reference to be equal to (or exceed) the lifetime of the future itself. Does that sound right? – Martin Jonsson Jan 07 '23 at 05:23

0 Answers0