It seems that you are dealing with an unnameable closure type. Afaik you can only deal with this with type erasure (a.k.a. dyn Trait), but I would wait to see if someone else has a more concrete answer.
The problem is the supertrait bound which is too eager. If you remove it, it compiles. (I also simplified the lifetimes a bit using GATs.)
Now, of course, the supertrait bound is convenient for the implementor. An (arguably) nicer (?) solution would be to keep the supertrait bound and add the necessary constraints to the signature of TaskCreator::spawn_task().
Removing supertrait bound causes FN to stop being a FnOnce and wrap_task can't invoke it anymore (playground). So, this doesn't really help me...
True, I didn't really know what I was doing. See updated code in playground link above -- I still can't get it to compile. :-/
Note: this code is a significantly reduced version of real-world code where wrap_task is doing some extra work in parallel to inner (via select!) and (once inner is finished) needs to use ctx. Right now that code make a clone of ctx (and fn_inner takes ctx by value), I tried to change fn_inner to take &ctx to avoid somewhat expensive clone. But I didn't expect to open a huge can of worm and spend 4 days on this seemingly trivial thing.
Doesn't really help me -- this forces me to discard one level of indirection (hiding TaskCtxImpl behind impl TaskCtx). Real TaskCtxImpl and TaskCreatorImpl have bunch of type parameters (with some extra bounds) and using FN: for<'a> AsyncCB1<&'a TaskCtxImpl<...>, T> instead of FN: for<'a> AsyncCB1<&'a TaskCtx, T> causes these parameters (and bounds) to spill into client code (foo and test functions) defeating the whole purpose of aforementioned indirection and turning client's code into hell.
You could play with this here, if you want -- I added just one generic parameter to these structs and it is already a major PITA.
Yes, sure. The example I posted was not intended as a solution to your specific problem, but to show that there is probably a problem in rustc that appears to be caused by refering to the unnormalized AT (Self::TaskCtx).^^
This works -- client side (foo and test functions) got a bit longer, but I could live with that. Thank you!
Nope, not gonna cut it :). Thankfully, @quinedot proposed a good workaround.
This was quite a ride... Tried to pass one value by reference -- ended up stepping on three mines (async fn can't meet FnOnce bound, closure issue and normalization problem). All problems are way too advanced for my current level, unfortunately. Thank you for your help again, @quinedot .
Edit: nevermind, using trait Task { type R; async fn run(self, ctx: &impl TaskCtx) -> Self::R; } makes everything simple (and I don't need to deal FnOnce problems).
@quinedot ... and I ran into another problem -- fn_task that gets passed into spawn_task needs to have a state. In previous code snippets I used fn to avoid the "closure issue" and assumed that in real-life code (where task struct arrives from outside) I'll just wipe up a manual implementation of a closure. Smth like this:
Unfortunately, manual implementation of FnOnce is experimental and even when enabled -- doesn't allow associated types (compilation fails). I guess another approach to this is to get rid of FnOnce completely and implement fn_task invocation manually via trait.