I have a task that needs to do some work, sleep for a bit (some logic to calculate how long), then repeat.
If this was old-school synchronous code on the JVM (wasting a thread) I'd be concerned about blowing up the stack by having the function call itself (in Java every method call is added to the stack, I would expect the same to be true of at least debug-enabled Rust), so I'd probably want to put it in an infinite loop or check that my language/compiler was able to do tail call optimisation. But with Rust's async keyword do I need to be concerned about any of those things?
I'd also be worried about lifetime scopes in this case, I'd want to make sure that all teh appropriate drops are called every time I recurse.
partially habit, partially information management and termination criteria. Of course they are equivalent so it boils down to a matter of style. For sure there are cases where a tail call optimised form of an algorithm is cleaner, and for those I'd use tailcall - Rust to guarantee the optimization actually happens.
Given the pinning requirement (c.f. the selected answer to this question) for doing this sort of thing when going async, it for sure feels like a (potentially infinite) loop is preferable.
The async recursion support is only letting async recursion type-check successfully, not any support for tail call elimination. If you write a tail recursive async function you’ll indefinitely accumulate Boxed copies of your future (and they will become increasingly slow to poll because each Future::poll() must recurse to the end of that chain anew).