[Post-RFC] Stackless Coroutines


Say you have a handful of coroutines and they all want exclusive access to some state during their interleaved executions. Passing &mut state through the coroutine argument provides an elegant way to solve this problem without the need for RefCells.


We can actually use the semantics of the implicit argument (excluding the implicitness) and apply them to all arguments to generators. So modifying vadimcn’s proposal in this way would mean that yield returns (), but arguments refer to value given by the latest resumption. Effectively this makes yield assign to all argument bindings (although that isn’t what happens at runtime). Borrows of arguments must not cross suspend points, which is only a restriction in immovable generators. This allows arguments to contain types which cannot cross suspend points (like for<'a> &'a T) and also wouldn’t consider arguments part of the generator frame (which is used for OIBIT trait selection). It also avoids the asymmetric |mut arg| { (arg,) = yield; } or the very noisy (a1, a2, a3, ...) = yield.


I think I had something like this in my RFC under Implicitly binding coroutine arguments. One relatively small drawback is that it’d be harder to write a macro that wraps yield and needs to use the passed-in value(s).

BTW, here are some other ideas I’ve had for dealing with let (arg,) = yield:

  1. Implement Deref/DerefMut on 1-tuples. You’d then write let arg = yield, and the arg would be a tuple, but auto-deref would allow you to use it in place of the inner value like 90% of the time. The rest of the time, you’d have to use arg.0.
  2. Alter type inference such that 1-tuple containing type T is allowed to unify with T and insert automatic tupling/un-tupling if that happens (a more transparent version of #1, basically). I am not sure about the overall impact of this on type inference, however.

let (arg1, arg2, arg3) = yield doesn’t bother me nearly as much as the single-argument case.


We know the “arity” of a coroutine before we type-check its body. It seems to me that we can just special-case the single argument case, as well. I could live with that myself.

The only problem I could imagine is that “code generation utilities” would also have to cope with this irregularity. It’s unclear to me how big of a problem this would be.


I’ve opened an experimental RFC for stackless coroutines as a result of the discussion in today’s compiler team meeting. The intention here is that this is explicitly experimental and pretty hand-wavy on the details. A future RFC will be required for stabilization but hopefully this RFC can go through the process much more quickly.



let x = yield.0;

be quivalent to

let (x,) = yield;


What if we want to eventually have other types than tuples as arguments, so we can e.g. emulate named arguments with anonymous structs? (if not then special casing single arg tuple is ok I guess)


I think the problem with the implicit binding alternative is dealing with branches with moves:

|a1| { // `a1: !Copy`
    if flip_a_coin() {
        yield x;  // actually means `let (a1,) = yield x;` via implicit magic or whatever
        // `a1` is valid here
    // `a1` is not valid here

There are probably solutions to this, but apriori they seem incongruous normal rust syntax, or at least less teachable.