You have uncovered a not-so-pretty aspect of AsyncFns, and issue with the hidden ()-call sugar, which tries to use whichever of Fn{,Mut,Once} is more permissive (here AsyncFn{,Mut,Once}).
- whilst the sugar and illusion works out in the
Fn{,...} cases (since no matter how the invocation happens, the output is always the same), in the AsyncFn{,...} cases, that aspect is broken, and the illusion then comes with a loss of direct expressibility 
In the AsyncFn{,...} cases, the output of the ()-call depends on how it was called / which flavor of the three AsyncFn{,...} was picked.
Most notably, in your case, the let mk_task = async move || { ... } boils down to defining:
struct Closure {
num: Arc<u64>,
}
impl Closure {
fn call_async_fn(&self) -> impl use<'_> + Future<Output = ()> {
return async move /* self: &'_ Closure */ {
sleep(Duration::from_millis(*self.num)).await
};
}
fn call_async_fn_once(self) -> impl use<> + Future<Output = ()> {
return async move /* self: Closure */ {
self.call_async_fn().await
};
}
}
let mk_task = Closure { num };
If you have a mk_task: Closure:
-
and try to do tokio::spawn(mk_task.call_async_fn()), you will run into the issue that the spawned task = mk_task.call...() is an impl use<'borrow_of_mk_task> + Future, that is, a future which is borrowing from mk_task (because it wants to &-borrow the num: Arc<u64>), and since mk_task is a short-lived local, such a borrow cannot be 'static, so the task: impl Future itself cannot be : 'static.
-
but if you do tokio::spawn(mk_task.call_async_fn_once()), you will not run into any issues whatsoever, because the so obtained task = mk_task.call...() is now a fully (lifetime-)standalone impl use</* no 'strings attached */> + Future (because it now owns the num: Arc<u64> inside of it), so it gets to be : 'static.
And when using the stdlib/blessed AsyncFn{,...} traits, .call_async_fn(), .call_async_fn_mut(), and .call_async_fn_once() are all "called" (using) simply (): mk_task().
- Herein lies then the issue: Rust will pick one implicitly in your stead (and not give you a direct/succint way of picking something else!), all following the usual heuristic guideline that the non-async
Fn{,...} traits had: the less we consume/use the receiver (the mk_task instance), the better. But here, given the lifetime difference, it's not always better!
Your question was about comparing the output of an async closure to an async block. It turns out they're almost the same, and in fact, in the AsyncFnOnce, they will be the same. But in the other AsyncFn{,Mut} cases, the returned impl Future may be borrowing from the captures if Rust considers it can do that optimization (here is where there is no direct/succint way to truly prevent this, since usage of move applied to the first layer of suspension (that of ()-invocation), not the the second layer of suspension (that of .awaiting)).
A to the workarounds, what @kpreid mentioned: you'll want to somehow "forget" you had something as flexible as an AsyncFn, and instead, try to end up with an AsyncFnOnce, if anything, at least during the call.
- Here, because our capture is not
Copy, a cleverly placed { ... }-braced block allows to force a move upon call, and therefore, to restrict the closure to be own-consuming, i.e., ...Once.
- But if the capture were
Copy, this trick would not work, and you'd be forced to use these uglier fn ... AsyncFnOnce funneling/helper functions 