1

I want to make a function that creates some internal state, lets a user to modify it, and then proceeds to do other stuff. This works fine in the normal case but I also want to give the user an opportunity to do async stuff in their computation. When the future constraint comes into play everything goes to hell:

#![feature(trait_alias)]

use core::future::ready;
use core::future::Future;
use core::sync::atomic::AtomicU8;

// A function that lets the user modify an inner value.
fn mod_ref(f: impl for<'a> FnOnce(&'a AtomicU8)) -> u8 {
    let a = AtomicU8::new(3);
    f(&a);
    a.into_inner()
}

// Same function but async.
trait AsyncW<'a, F> = FnOnce(&'a AtomicU8) -> F where F: Future<Output = ()>;
async fn mod_w_async<F>(f: impl for<'a> AsyncW<'a, F>) -> u8 {
    let a = AtomicU8::new(3);
    f(&a).await;
    a.into_inner()
}

fn main() {
    // This works fine.
    mod_ref(|_| ());

    // error: implementation of `FnOnce` is not general enough
    mod_w_async(|_| ready(()));
}

playground

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:27:5
   |
27 |     mod_w_async(|_| ready(()));
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `FnOnce` is not general enough
   |
   = note: closure with signature `fn(&'2 AtomicU8) -> std::future::Ready<()>` must implement `FnOnce<(&'1 AtomicU8,)>`, for any lifetime `'1`...
   = note: ...but it actually implements `FnOnce<(&'2 AtomicU8,)>`, for some specific lifetime `'2`

Why is this happening and is there a way to get it to work?

Note that mod_ref and mod_w_async are cut down for brevity. The important part is that the argument function takes as an argument a reference to the function's stack (or rather future's state machine).

user1635327
  • 1,469
  • 3
  • 11
fakedrake
  • 6,528
  • 8
  • 41
  • 64
  • 1
    This is missing an `await` call at the end. That won't fix the problem but will narrow the error down. `mod_w_async(|_| ready(())).await` – tadman Aug 13 '22 at 01:45
  • Thanks but I didn't add it on purpose because main is not async. As it is it should just return the future and drop it without running it. – fakedrake Aug 13 '22 at 01:48
  • Often the compiler doesn't trust your `async` function to be done with the content, so it can be easier to just return it from the function instead when complete. This will satisfy the borrow checker. As in, `let a = fn(a).await` and make that return what it receives. – tadman Aug 13 '22 at 01:48
  • Remember, if you don't call `await` the code doesn't run, and in `main()` you need it `async` or it's all a mess of junk code. Consider `#[tokio:main]` or whatever system you're using for async. – tadman Aug 13 '22 at 01:48
  • I don't quite understand what you are suggesting in your second to latest comment, the problem seems to be the typechecker and not the borrow checker right? – fakedrake Aug 13 '22 at 01:52
  • It appears to be the typechecker because the typechecker wants a lifetime that's more lenient and it's not getting one, and that lifetime issue originates from the borrowing situation caused by `(&a)`. I'm suggesting the simple approach that works around references by passing in via move, and getting it back via move. No mess, no confusion or ambiguity. – tadman Aug 13 '22 at 01:54
  • It's not really an option unfortunately, in the real scenario the referred value involves a mutex and is shared with other threads. I tried wrapping the reference in a type but I still needed the quantified lifetime parameter and the same error occured. – fakedrake Aug 13 '22 at 08:34

0 Answers0