The current implementation of
async fn and
await! depend on
thread_local! as a work-around to what appears to be a fundamental shortcoming of the existing generators implementation which also prevents these features from being used within
async fn is currently implemented as a generator that yields whenever it needs to
await!. This generator takes no arguments and produces a final result as one would expect.
When the generator is evaluated, there is no mechanism to pass the future context into the generator and thus no means by which the
await! macro can determine the context under which it should poll its future. This appears to have been done so that an
async fn can be written without any explicit mention of a context for improved ergonomics.
The current implementation works around this by using a
thread_local! pointer to refer to the topmost context in the call stack which can be used by the
await! macro. This alone prevents the async/await from use in
It seems that the solution we currently have is an indicator in the shortcomings of the existing generator implementation. The thread-local pointer is doing work that is already achieved by the call stack and the current semantics of
yield do not seem to entirely match the semantics of the problem they were built to solve.
A co-routine yields when it progress becomes dependent on a resource which is currently unavailable. The current semantics of
yield prevent any information being passed back to the generator when that resource becomes ready.
Some existing suggestions have been made for bi-directional moves to occur upon a yield. However the types at the input and output at every yield point in a generator would need to be identical with a known size and static type (assuming no
alloc) which would be neither ergonomic or practical. It would also require that a generator have a different entry-point for being started from that used to resume it.
Instead, I would propose a change to the internal semantics of borrows within a generator. Currently, borrows to data which has a lifetime bound by the instance of the generator are not allowed to live across a yield statement so as to ensure they remain valid (I suspect this is because the entire generator, and thus the variables within its context, can have its location moved between the yield out of the generator and the resumption of the generator).
I think that this is perhaps unnecessarily strict and instead could be replaced with a rule stating that only borrows to data with a lifetime that is not bound by the generator may exist when a generator is resumed. This would imply that borrows may exist after a yield. Furthermore, this would allow for a reference to a value with a lifetime bound by the generator to be yielded (the existing borrow checker rules should enforce that the generator cannot be resumed until all such borrows are dropped).
This would allow for a substantially more useful implementation of
await! that could be used without any dependency beyond
await! would augment its future such that the result is stored inside the generator rather than being lost to its caller and yield a reference to the augmented future. The implementation of
poll for an
async fn would then expect a trait object
&mut Future<Output = ()> whenever the generator yields. It would poll that future to completion in place of the generator (the final result of that future would end up being stored within the generator itself removing any need for dynamic allocation). After completing the intermediate future,
poll would then resume the generator which would use the result of the future that had been completed.
Edit (2018-09-23): The original content of this post is below. It was changed to better frame the issue and a possible solution.
The current implementation for async/await was implemented using thread-local stroage for ease of implementation, however this precludes the use of this feature in
I’d like to make a pull-request resolving this issue, but it appears there is no actual github issue relating to this. Should I create an issue before making the pull request?