Executing an async block is similar to executing a closure expression: its immediate effect is to produce and return an anonymous type.
Note: The future type that rustc generates is roughly equivalent to an enum with one variant per await point, where each variant stores the data needed to resume from its corresponding point.
IMO, the difference from the closure type here is, that async block should store the variable i, w in its field because they are needed after #1 is resumed, that is, the anonymous type of the async block should have a field of type Wrapper<'b>. Specifically, it may look something like this:
So I wonder why the type of the async block can satisfy 'static?
This async block doesn't borrow any data from its environment; it's self-contained.
Of course, if you were writing the type of the future yourself, you'd have to include the lifetime parameters of the types of the local variables. However, you'd quickly run into problems because you'd discover that the future is self-referential.
The compiler doesn't literally generate a regular enum (that the explanation describes); it generates its own data structure that it knows is self-referential but safely/soundly usable. There are no literal lifetime annotations that you expect, because they would be impossible to express in safe Rust.
I haven't deep-dove on async generator construction like I have various other aspects of Rust, so treat this reply accordingly.
Once you start polling the future, it may become self-referential -- the state may involve references into its own captured data. This is only ok if said references can't become invalid by e.g. moving the data, and that's what Pin and Unpin are about. A future that has self-referential state will not implement Unpin.
The type can still be 'static because the compiler-generated type doesn't act like a structure with lifetime parameters. Imagine it's accomplished with raw pointers at the entry and exit points of the generator, say. It's done in such a way that the future can't be used unsoundly with safe code, modulo compiler bugs.
You need unsafe to implement such a future, generally.
There may be some niche exceptions involving leaking or whatnot, I haven't played with it. ↩︎
Not everything about the async output is as simple(!) as a closure. There cases where what state is held across an await point legitimately makes implementing Send or Sync unsound, even though the directly captured data would allow it (e.g. holding a MutexGuard across an await point). And the compiler also still has some rough edges / isn't smart enough, and sometimes the async output doesn't implement Send or Sync or such even though it could.
There may be lifetime related rough edges or complications too, but I'm not sure offhand.
Well, I just mean considering whether the type of an async block satisfies a lifetime trait bound is analogous to considering them for closure, right? In other words, the lifetime parameter within the block does not impact whether the type of the async block satisfies a lifetime trait bound, which is similar to closure.