Second Draft: Coroutines

Richard (Rikki) Andrew Cattermole richard at cattermole.co.nz
Fri Jan 24 23:22:19 UTC 2025


On 25/01/2025 9:56 AM, Sebastiaan Koppe wrote:
> On Thursday, 23 January 2025 at 23:09:42 UTC, Richard (Rikki) Andrew 
> Cattermole wrote:
>>
>> On 24/01/2025 10:17 AM, Sebastiaan Koppe wrote:
>>> On Thursday, 23 January 2025 at 20:37:59 UTC, Richard (Rikki) Andrew 
>>> Cattermole wrote:
>>> You don't need to describe how scheduling works, just the mechanism 
>>> by which a scheduler gets notified when a coroutine is ready for 
>>> resumption.
>>>
>>> Rust has a Waker, C++ has the await_suspend function, etc.
>>
>> Are you wanting this snippet?
> 
> No, not specifically. I am requesting the DIP to clarify the mechanism 
> by which a scheduler is notified when a coroutine is ready for 
> resumption, not the specific scheduling itself.

It should be scheduled:

If: tag >= 0

And: waitingOnCoroutine == None || (waitingOnCoroutine != None && 
waitingOnCoroutine.isCompleteOrHaveValue)

Where isCompleteOrHaveValue is: tag < 0 || haveValue

The DPI does not require you to do any of this (if things are written 
correctly it should not segfault and hopefully won't corrupt anything), 
but this would be good practice. And yes it is library code. The 
compiler does not help you to do any of this. You the library author are 
responsible for it.

If you want to do something different like a waker style where these 
rules do not apply, you are free to. The language only requires the tag 
to be >= 0 due to the branch table stuff.

> The snippet you posted raises more questions than it answers to be 
> honest. First of all I still don't know what a GenericCoroutine or what 
> a Future is.

I wrote it out for someone else here: 
https://forum.dlang.org/post/vn14i8$1g46$1@digitalmars.com

```d
struct GenericCoroutine {
	bool isComplete();
	CoroutineCondition condition();
	void unsafeResume();
	void blockUntilCompleteOrHaveValue();
}

struct Future(ReturnType) : GenericCoroutine {
	ReturnType result();
}

struct InstantiableCoroutine(ReturnType, Parameters...) {
	Future!ReturnType makeInstance(Parameters);
	InstantiableCoroutine!(ReturnType, ughhhhh) partial(Args...)(Args); // 
removes N from start of Parameters

	static InstantiableCoroutine opConstrucCo(CoroutineDescriptor : 
__descriptorco)();
}
```

https://github.com/Project-Sidero/eventloop/tree/master/source/sidero/eventloop/coroutine

If it doesn't work for you, do it a different way. The language has no 
inbuilt knowledge of any of these types. It determines everything that 
it needs from the operator overload and the core.attributes attributes.

If it turns out those attributes are not enough (I am not expecting any 
to be needed), we can add some to allow your library to communicate to 
the compiler on how it needs to do the slicing and dicing of the 
function into the state object that you can consume and call.

> It seems that in your design coroutines are only able to wait for other 
> coroutines. This means you need to model async operations as coroutines 
> in order to suspend on them.

It should be coroutines, but I left out the filtering for the type that 
the ``await`` statement will accept. It'll chuck whatever you want into 
the sumtype value. Its your job to filter it. If you want to support 
other types and behaviors go for it!

Remember ``await`` statement does two things, assign to ``waitingOn`, 
then yield (aka return) (and set tag appropriately).

 > Why was this done? C++'s approach of having
 > an awaiter seems simpler.

This is the C# approach.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions#1298-await-expressions

A significantly more mature solution where we have Adam who has 
experience working with it since it was created in teams. He has dealt 
with all the problems that come with that. I don't have a stake holder 
who fits the bill for other styles.

In saying all that, I find the dependency approach to be very intuitive, 
and I was able to implement it purely off of first principles. Whereas 
the other approaches including C++ is still after much reading not in my 
mental model.

 > For one it allows the object you are awaiting
 > on to control the continuation directly.

If you want that for your library to look at the ``waitingOn`` variable 
for control over scheduling, go for it! Nothing in the DIP currently 
should stop you from doing that.

You could even add support for it as part of instantiation of the 
coroutine! Its your library code, you can do whatever you want on this 
front.

You control execution of the coroutine itself, you can see that this 
value was set. You can inspect it, you can call whatever you like.

That is what the last example with ``
void execute(COState)(GenericCoroutine us, COState* coState) {`` shows. 
You are fully in control over the coroutines execution the language is 
focused solely on the slicing and dicing of the function into something 
that a library can then call.

The language defines none of this _on purpose_.



More information about the dip.development mailing list