I posted this on SO and didn't get any useful responses so I thought I would post it here as well, I hope I am not violating community rules. If I am, I appologise
I want to make a function that creates some internal state, lets the user modify it and then proceeds to do other stuff. This works fine in the normal case but I also want to give the user the 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(()));
}
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).
Heh, this could deserve being quoted: it's actually a real phenomenon in the Rust language; I might go into details explaining this later in the post.
In this instance, however, the first and foremost culprit seems indeed to be the trait alias. That is, if we go and focus on mod_ref (no async whatsoever!), but using a trait alias layer:
trait SyncW<'a> = FnOnce(&'a AtomicU8) -> ();
// A function that lets the user modify an inner value.
fn mod_ref(f: impl for<'a> SyncW<'a>) -> u8 {
let a = AtomicU8::new(3);
f(&a);
a.into_inner()
}
we end up with the same error message about "not general enough".
Now, higher-order ("general enough") closures is indeed a subtle topic, and one where the code is very sensitive to what should have otherwise been semantically equivalent changes.
I was aware that the "stable Rust trait alias hack/polyfill" suffered from such limitation, but it is interesting (and disheartening) to see that even "proper" trait aliases suffer from it as well.
So your best solution here is to copy-paste the actual bound in your API.
FWIW, if the caller explicitly annotates the type of the input as &_ (e.g, |_: &_| or |x: &_|), then that error goes away.
but that's where your initial post would kick in: it's still very likely to cause problems with async for different but related issues.