Skip to content

Hello Coro

Simon Krajewski edited this page Dec 30, 2025 · 1 revision

Hello Coro

// Main.hx
import hxcoro.Coro.*;
import hxcoro.CoroRun;

@:coroutine function fancyPrint(text:String):Void {
	for (i in 0...text.length) {
		Sys.print(text.charAt(i));
		delay(100);
	}
	Sys.print('\n');
}

@:coroutine function test() {
	fancyPrint('hello');
	delay(1000);
	fancyPrint('world');
}

function main() {
	CoroRun.run(test);
}
haxe -lib hxcoro -cp source --run Main
cmd_LAeNcQ36qt.mp4

There's a lot going on here internally, which we'll document in detail eventually. For now, the main takeaways are:

  • @:coroutine function defines a coroutine. A coroutine is a function that allows suspending its execution and resuming it later. In our example above, all calls to delay suspend execution for the given duration in milliseconds.
  • In order to support this, there must be some execution magic going on in the background, which requires us to enter "coroutine world". This is done via the CoroRun.run(test) call. This is similar to entering "macro world" when calling a macro function.
  • Coroutines can call other coroutines, such as test calling fancyPrint in our example. They can also call any non-coroutine function, e.g. fancyPrint calling Sys.print. In both cases, execution works from top to bottom as one would expect in normal programs.

Concepts

One of the biggest challenges when learning about coroutines is terminology. There are multiple concepts that build on each other and sometimes it is difficult to understand where one ends and another begins, especially when dealing with error messages. We will give a brief overview here to show that all these things are rather finite:

Coroutine

We call a @:coroutine function coroutine. While this might seem trivial, there can be quite a bit of confusion when researching this concept in other languages.

Continuation

A continuation can be thought of as a callback to be called once something finished execution. This is sometimes called continuation passing style and can easily be used in normal code:

function doSomethingThenCallMe(continuation:() -> Void) {
	trace("doing something");
	trace("did something");
	continuation();
}

function main() {
	doSomethingThenCallMe(() -> {
		trace("thanks!");
	});
}

When working with coroutines, continuations are used internally to manage the flow of execution. This is usually not visible to users of coroutines, but might leak in some instances.

Suspension

Suspension means pausing execution of a function, generally with the intent to resume it later.

Suspension point

A suspension point is a place where the execution of a coroutine can be suspended, which is typically a call to another coroutine.

Resuming

Resuming generally means continuing execution that was paused.

Scheduler

A scheduler drives the execution of the "coroutine world" by managing continuations.


We briefly suspend the terminology overview here to tie everything together in a single sentence: A coroutine is a function that executes code until it reaches a suspension point, where it might suspend its execution so that it can later be resumed when the scheduler calls its continuation.

Concepts continued

Task

A task is a higher-level concept on top of coroutines. The call to CoroRun.run(test) creates a single task that handles the execution in "coroutine world". Tasks can have children, which we'll learn about later.

Context

A context is an immutable map of values that is tied to a task. For example, the scheduler is a context element of a given task.

Cancellation

Tasks and some continuations can be canceled, which prevents further execution. Cancellation here is cooperative, which means that code is expected to react to a cancellation request instead of forcefully being terminated from the outside.

Clone this wiki locally