Skip to main content

Kotlin Coroutines: How SelectBuilder magically pulls coroutines out of the select block

I was puzzled for a while about the syntactic sugar around select — Kotlin's experimental feature to process streams from multiple sources (docs). Take a look at this:

fun main() = runBlocking<Unit> {
	select<Unit> {
		launch {
		}.onJoin { Unit }
		produce {
		}.onReceive { Unit }

Note that the on... methods aren't suspending, but the async block is run somewhere somehow, so it seems like something that smells a lot like suspend (Deferred, Job, Channel, ...) -> Unit must be happening behind the curtains. The signature of select looked like it answered this mystery:

Code samples below and beyond come from kotlinx.coroutines v1.0.1

// from selects/Select.kt
public suspend inline fun <R> select(crossinline builder: SelectBuilder<R>.() -> Unit): R

Ah, so SelectBuilder<R>.() binds this within select's block to a SelectBuilder, so that's how it grabs up those coroutines! ...right?

But that's obviously not quite enough. Each of those on... calls looks totally siloed; after all they evaluate to Units, and there's no mention of this anywhere! launch(...) and produce are still CoroutineContext.launch(...) and CoroutineContext.produce(...), so at first glance, it actually looks like SelectBuilder<R>.() does squat.

Things need to get even stranger before they get clearer. Looking at the definition of any of the on... methods reveals they're actually... values?

// from src/Job.kt
interface Job: SelectClause0 {
	// ...
	 * Clause for [select] expression of [join] suspending function that selects when the job is complete.
	 * This clause never fails, even if the job completes exceptionally.
	public val onJoin: SelectClause0
	// ...

And these values are... this??

// from src/JobSupport.kt
class JobSupport : Job {
	// ...
	public final override val onJoin: SelectClause0
	        get() = this
	// ...

What. This is to say some_coroutine { } .on...(<clause>) is really some_coroutine { }(<clause>)? We need to look at that select implementation pronto. It's actually very terse; to protect against changes in the source I'll just point out that it constructs a SelectBuilderImpl as the context of the select clause, so let's peek at that:

// from src/Select.kt
class SelectBuilderImpl {
	// ...
	override fun SelectClause0.invoke(block: suspend () -> R) {
	    registerSelectClause0(this@SelectBuilderImpl, block)
	// ...

Oh. Well that was a fairly abrupt conclusion. We do call the objects themselves so that the context SelectBuilder can swap out the invoke for one that registers the object to the context. This is because all the coroutines are one of (SelectClause0, SelectClause1<Q>, SelectClause2<P, Q>) depending on their outputs — Job : SelectClause0 for example.

That's a pretty clever one JetBrains. Above preserving the no-frills lambda syntax we love for those on... invocations, it also avoids careless/rushed/human developers forgetting to register our coroutines with select. I'm not really aware of other languages that allow you to do this; are there other examples of scoped class extension? It's nuts in my opinion. Absolutely nuts.