Intro

As a means to better understand Kotlin’s coroutines by way of my prior understanding of Go’s goroutines, I wrote gotlin which reimplements the goroutine examples from The Go Programming Language book in Kotlin with coroutines.

In no way do the authors of “The Go Programming Language” endorse this or this use.

This post assumes you’re at least vaugely familiar with Go, goroutines, Kotlin, and Kotlin’s coroutines. If you’re not, here are some resources before diving in:

Why?

Coroutines can be challenging to understand, leading to potential issues when used incorrectly. I cannot cite examples here but I have reviewed the severe event retrospectives where, for example, the root cause was improper use of runBlocking within another runBlocking. This has lead some engineering orgs to have an irrational fear of coroutines and of Kotlin as a language as a whole. Concurrency is hard, but it’s not impossible and should be understood, not feared.

Because of this, I set out to better understand coroutines myself and I struggled as well. While watching a talk that demonstrated the use of Kotlin’s select it clicked that part of my misunderstanding of coroutines is that they’re very similar to Go’s goroutines. I was treating them too similarly in my head. While at the same time, I didn’t have many (any?) great practical examples of coroutines in use.

I set out to better understand coroutines by directly comparing them to the goroutine code examples in The Go Programming Language book. While I only occasionally use Go professionally, I think this book is absolutely top-notch; easily one of the best programming language books on my shelf.

Suggested use

Granted, this is targeted at a pretty niche category:

  1. Developers who know Go and its goroutines reasonably well
  2. Also don’t know Kotlin’s coroutines reasonably well

That said, the way to use it is:

  1. Have a copy of the book
  2. Pull up the book’s source code
  3. Pull up gotlin and compare side-by-side

Lessons learned

There are more details in the gotlin README but here are some higher level things I learned

First, just like in grade-school, let’s compare and contrast the two:

Compare

  • Both provide/are a super lightweight alternative to Threads
  • Both support sharing memory by communication (rather than communicating via shared memory)

Contrast

I think this is the key point: Many of the differences between goroutines and coroutines come down to philosophical design differences between the Go and Kotlin programming languages.

Go / goroutines

Go emphasizes simplicity, practically above all else. For example, its runtime has a garbage collector, but very few ways to tune it. Of course it has for-loops, but no functional equivalents of map/reduce/filter. Go’s philosophy seems to be to prefer to remove what can be removed from the language and only add features when there’s been a clear demonstrated need over time. This is in stark contrast to many languages that absorb features quite easily, like C++. Which of course makes sense given that it was born out of dissatisfaction with C++. This explains why it took so long for Go to support Generics.

Back to goroutines, this minimalist philosophy shows up in how goroutines work:

  • Goroutines baked into the language via go, as are channels. This allows them special in/out channel syntax and select
  • Goroutines are unmanaged. Go doesn’t give a way to cancel them and will let you exit your program without closing out goroutines.

Kotlin / coroutines

On the other hand, Kotlin is anything but minimalist. I’d consider Kotlin a maximalist language with pragmatic defaults. Kotlin tries to provide what’s likely best for you in short reach (e.g. collections are immutable by default) but lets you run with whatever paradigm you’re most comfortable with among imperative, object-oriented, functional, or declarative (via DSLs). It leans toward immutable functional-spiced value-data-oriented programming, but it doesn’t impose a philosophy unto you. Much like its ancestor, Java, it provides managed functionality.

Along with that philosophy in mind, some distinctions around Kotlin’s coroutines are:

  • Coroutines are provided via a library, kotlinx.coroutines, and not within the language itself. It’s a testament to Kotlin’s flexibility that it can just bolt-on coroutines like this.
  • This “bolt-on” approach does mean there has to be a bridge between “normal” Kotlin and “coroutine” Kotlin. This is runBlocking and it’s important to not step through this bridge twice!
  • This “bolt-on” also results in limitations, e.g. select cannot have clauses send and receive from the same channel.
  • Kotlin’s coroutine library also provides some pre-built common patterns, unlike Go which expects you to build them yourself:
    • launch and async return Job and Deferred, which allow for management of the child coroutines. This includes the ability to cancel, which Go does not directly support.
    • coroutineScope where any child coroutine failure causes them all to fail
    • supervisorScope for isolating child sub-coroutine failures

Project learnings

I took on this project with two goals, both intentionally limited in scope:

  1. Better understand Kotlin coroutines so I can use them effectively
  2. Hopefully provide a resource to help others also better understand

I will have to wait and see if #2 is met, but I definitely met my #1 goal.

A large part of that was due to the very helpful expert PRs from dump247. Going into this, I expected it to be a self-study, code, then release process. I tend to run solo and I never really understood “study groups.” I didn’t even consider or expect anyone else to contribute, but I’ve learned more from his contributions than I would’ve on my own.

What’s next?

I’ve left room for additional enhancements.

Suggestions and pull requests are welcome! I continue to learn and welcome any constructive input.