Passing borrows+lifetimes up into a generator through resume

I'm looking at how to implement actor coroutines for my Stakker crate. I asked about this on users but I guess no-one there knows enough about async/await or generator internals.

All my actor methods receive two references: a reference to the actor state structure &mut Self and a reference to a context for various other actor operations &mut Cx<'_, Self>. In my coroutine I want the code to have access to the same two references. So looking at it in the coroutine code before the transformation, it could look like two reference variables this and cx which change values at every yield/await, and where the lifetimes on the types only last until the next yield/await. However, as far as I can see there is no way to represent that lifetime in an async or generator block right now, e.g. an 'until_next_yield lifetime.

However after transformtion (as in the example in the "generators" section of the Unstable book), the lifetime I want is easy to represent, and it would be easy to pass my references by patching in two extra arguments to the resume function. The lifetime I want is just the normal lifetime that reference arguments get.

I guess what I'm trying to do is impossible right now in Rust (on either stable or nightly -- as far as I can see anyway). For example, I've considered the following approaches:

  • Try to return my two references from a Future that I .await on -- the problem being that there is no 'until_next_await lifetime that I can give them to make them safe. Also, Future's Output type doesn't support lifetimes.

  • Try to access my two references through a glue structure (e.g. behind an Rc) and unsafe code -- again the problem would be having an 'until_next_await lifetime to stop references escaping

  • Try to modify the actor coroutine code with a proc macro so that access to the references can be encapsulated in smaller regions where lifetimes can be represented and controlled. It might work but it has lots of limitations though.

Anyway, here are some theoretical approaches that might help me:

  • If passing context (borrows/lifetimes) up into a generator's resume function was generalized in some way, so that generator code could access the extra arguments to the resume function, e.g. have some syntax to access this context. Then maybe I could pass this and cx to the resume function, and access these as something like resume_context.this and resume_context.cx in the generator source (before transformation).

  • If I could access the generator or async-block source-code transformation that the Rust compiler does, e.g. through a new method in the proc-macro crate, I could do the transformation myself within a proc macro, then patch in my two extra arguments, and then the borrow checker will take care of the lifetimes for me. (But this assumes that it is a token-level transformation; perhaps you do it at a deeper level than that?)

  • Failing all that, maybe I could re-implement or copy the code that does the transformation that converts an async block into the state machine in my own proc macro, in which case I can easily get it to pass through and access the references, and the borrow checker will take care of the rest.

Really, I think generalizing passing context to generators and accessing it from within the pre-transformation code would be the cleanest way to do it, because it integrates borrowing between the inner and outer worlds (i.e. either side of the resume call). But I understand that probably no-one has time to implement and stabilize that because there are higher priorities. So that's fine.

However I do wonder whether anyone has any ideas about the shortest route for me to get what I need for my actor coroutines, assuming no new support from the Rust compiler. Really if I could copy/paste the generator transformation code that the compiler uses into a proc macro, that could work in theory. (I don't know the compiler internals, though, so I haven't looked for the code to see how hard it would be.)

Or is there a better way that I've overlooked? Any suggestions would be welcome. Thanks.

I think what you're looking for is

It's something that generators should support, but isn't yet implemented.

2 Likes

Okay, so generators are much closer to what I need than I imagined. I'll keep an eye on that issue. Thanks.