Why does the following code just print "salutations"? Why do async blocks desugar in such a way that code before the first await in the async block is deferred until the future is polled? Is this behavior guaranteed? It seems like it could constrain the lifetime of the future being created.
Async functions work differently from normal functions. When an async function is called, it does not enter the body immediately. Instead, it evaluates to an anonymous type which implements Future. As that future is polled, the function is evaluated up to the next await or return point inside of it (see the await syntax section next).
There is some history to this that I can't quite find right now, but this comment agrees that it constrains all captured lifetimes and suggests that this is intentional.
Because you haven't used any async runtime which may execute anything.
Because it's the only way to allow you to do what you did: write async which is not dependent on any async runtime.
Obviously.
Indeed. But making async runtime pluggable was deemed important enough that it was done. And without async runtime you can not actually run async functions. Which, pretty much ensures that async would work in a way it does.
Other languages, which actually have rich runtime, including support for async may do other choice, but Rust couldn't have anything else for obvious reason: before you link in async runtime there are literally nothing that may make you async code runnable… yet it still have to be compileable… somehow.
This is required for stacking Future combinators without allocation in-between each one, as after the first poll they can't be moved.
To demonstrate this, here is a concrete example of why the design demands that async fns are lazy:
async fn foo() {
let owned_data = [1, 2, 3, 4, 5];
for &i in owned_data.iter() { // iterator has a reference to `owned_data`
some_other_async(i).await;
}
}
As soon as .iter() is called, which happens before the first await point, the std::slice::Iter iterator is owned by the future and also borrowing from the future. Therefore, this code can't be executed until the future is pinned, which isn't required until the first poll().
In principle, Rust could define the async code transformation so that code that doesn't have any borrows of local variables does run before the future is polled, but that would be a more complex and potentially surprising execution ordering. The current one is simple: nothing happens until the first poll. If you want to run something before the first poll, you write a function that returns async {} instead of an async fn.